Iawen's Blog

我喜欢这样自由的随手涂鸦, 因为我喜欢风......

1. 从源码编译安装 Git

wget https://github.com/git/git/archive/v2.24.0.tar.gz
tar xzf v2.24.0.tar.gz && cd `tar tvf v2.21.0.tar.gz | head -1 | awk '{print $6}' | cut -d/ -f1`
make configure
./configure --prefix=/usr
make install

2. Server 和 Client 的设置与操作

2.1 Server

一个远程仓库通常只是一个裸仓库(repository)–即一个没有当前工作目录的仓库。

2.1.1 通讯协议

Git可以使用四种主要的协议来传输资料: 本地协议(Local), HTTP协议, SSH协议及Git协议。

A、本地协议

其中的远程版本库就是硬盘内的另一个目录。克隆本地版本库:

git clone /opt/git/project.git    #或
git clone file:///opt/git/project.git

仅指定路径, Git会尝试使用硬链接或直接复制所需要的文件。指定file://, Git会触发平时用于网络传输资料的进程, 那通常是传输效率较低的方法。

B、HTTP 协议

Git通过HTTP通信有两种模式。

  • 旧版本的(1.6.6之前)哑HTTP协议
  • 新版本的智能HTTP协议: 与SSH及Git协议类似, 只是运行在标准的HTTP/S端口上并且可以使用各种HTTP验证机制, 它即支持像git://协议一样设置匿名服务, 也可以像SSH协议一样提供传输时的授权和加密。
C、SSH 协议

通过SSH协议克隆版本库, 你可以指定一个ssh://的URL:

git clone ssh://user@server/project.git
git clone user@server:project.git   #SCP式语法

可以不指定用户, Git会使用当前登录的用户名。

D、Git 协议

这是包含在Git里的一个特殊的守护进程; 它监听一个特定的端口(9418), 类似于SSH服务, 但是访问无需任何授权。要让版本库支持Git协议, 需要先创建一个git-daemon-export-ok 文件。由于没有授权机制, 一旦开放推送操作, 意味着网络上知道这个项目URL的人都可以向项目推送数据。

2.1.2 搭建Git服务器

在开始架设之前, 需要把现有仓库导出为裸仓库。为了通过克隆仓库来创建一个裸仓库, 需要在克隆命令后加上一个–bare 选项。按照惯例, 裸仓库的目录名以.git 结尾, 就像这样:

git clone --bare my_project my_project.git  #整体效果大致相当于
cp -Rf my_project/.git my_project.git

#将裸仓库推送到服务器
scp -r my_project.git user@server:/opt/git

#创建一个空的裸仓库
git init --bare

如果一个用户通过SSH连接到服务器, 并且对/opt/git/my_project.git 目录拥有可写权限, 那么他将自动拥有推送权限。 如果到该项目目录中运行git init 命令, 并加上–shared选项, 那么Git 会自动修改该仓库目录的组权限为可写:

git init --bare --shared

如果需要团队里的每个人都对仓库有写权限, 有三种方法:

  • 1、给团队里的每个人建立帐号
  • 2、在主机上建立一个git 帐户, 让每个需要写权限的人发送一个SSH公钥, 然后将其加入git 帐户的~/.ssh/authorized_keys 文件。
  • 3、让SSH服务器通过某个LDAP服务

2.2 客户端

2.2.1 推送本地库到服务端

cd myproject
git init
git add .
git commit -m 'initial commit'
git remote add origin git@192.168.0.86:/data/website/project.git
git push origin master

#同时关联本地分支到远程分支
git branch -vv  #查看本地分支
git branch --set-upstream-to=origin/远程分支的名字 本地分支的名字

2.2.2 直接拉取远程仓库

git clone git@192.168.0.86:/data/website/project.git
# git clone --filter=blob:none

3. 基本操作

3.1 创建与合并分支

3.1.1 从master分支创建dev分支并切换到dev分支:

git checkout master
git checkout -b dev

其中, git checkout -b dev 等价于:

git branch dev
git checkout dev

查看本地当前的分支, 分支前面带“*”表示当前分支, 剩下的分支表示本地有的分支:

git branch

查看远程全部的分支, 白色的表示本地有的, 红色的表示本地没有, 仅在远程存在:

git branch -a

3.1.2 修改代码、提交代码(当前的操作是在dev分支上进行)

git add a.html git commit -m "提交文件a.html"

3.1.3 分支合并(将dev合并到master)

git checkout master git merge dev

3.1.4 合并完成后, 删除dev分支.(删除dev分支时, 注意我们当前所在的分支不能是dev分支)

git branch -d dev

3.1.5 删除后, 查看分支(此时看不到dev分支了)

git branch

3.1.6 修改最近一次提交的提交信息

git commit --amend

修改多个提交信息: git rebase -i

3.1.7 删除分支

# 本地强制删除分支issues1234
git branch -D issues1234

#  推到远程
git push origin :issues1234

3.1.8 filter-branch

从每一个提交移除一个文件, 如:

git filter-branch --tree-filter 'rm -f passwords.txt' HEAD

为了让filter-branch 在所有分支上运行, 可以传递 –all 选项。

使一个子目录做为新的根目录 git filter-branch –subdirectory-filter trunk HEAD

3.1.9 总结 : 工作中经常从master创建新的分支, 具体操作如下:

git checkout master 
git checkout -b issues1234 
git push origin issues1234 
git add .. 
git commit -m "***" 
git push origin issues1234

# 注意: 将本地分支branch1推到远端的branch2操作步骤: 
git push origin branch1:branch2

3.2 发布包

3.2.1 为发布打标签

git tag -s v0.0.3 -m 'new tag v0.0.3'

在为发发布打签前, 需要通过运行 gpg --list-keys 找出想要的key。

gpg --list-keys 
/root/.gnupg/pubring.gpg
------------------------
pub 2048R/4499CB9F 2018-06-04
uid iawen (publish the project) <iawen@126.com>

# 如果没有, 则需要生成
gpg --generate-key

之后, 可以通过管道传递给git hash-object 来直接将Key 导入到Git 数据库中:

gpg -a --export 4499CB9F | git hash-object -w --stdin

3.2.2 生成一个构建号

git describe

由最近的标签名、自该标签之后的提交数目和你所描述的提交部分SHA-1 值组成。

3.2.3 准备一次发布

git archive master --prefix='project/' | gzip > `git describe master`.tar.gz
git archive master --prefix='project/' --format=zip > `git describe master`.zip

3.2.4 制作提交简报

git shortlog --no-merges master --not v0.0.3 

v0.0.3是指上次发布的版本号

3.3 解决冲突

3.3.1 发生冲突的文件

<<<<<<< HEAD Creating a new branch is quick & simple. ======= Creating a new branch is quick AND simple. >>>>>>> feature1

其中, git使用 «««<, =======, »»»> 标记文件中自己和别人产生冲突的部分。

  • 在 «««<, ======= 之间为自己的代码;
  • =======, »»»> 之间为别人的代码。 如果保留自己的代码, 将别人的代码删掉即可。

3.3.2 冲突解决后提交

git status 
git add *** 
git commit -m "fix conflict" 
git push origin 分支名

3.4 Bug分支

3.4.1 储藏更改:将当前更改的代码储藏起来, 等以后恢复使用

git stash

3.4.2 恢复储藏的代码

# 恢复的同时把stash内容删掉
git stash pop       # 或者
git stash apply     # 恢复stash, 但是stash内容并不删除 git stash drop //在上面操作的基础上, 以此来删除stash

git stash list      #查看全部的stash列表。

3.4.3 将stash空间清空

git stash clear

3.4.4 git stash pop 和 git stash apply 区别

原来git stash pop stash@{id}命令会在执行后将对应的stash id 从stash list里删除, 而 git stash apply stash@{id} 命令则会继续保存stash id。

3.5 版本回退

3.5.1 回退至上一个版本

git reset --hard HEAD

3.5.2 回退至指定版本

git reset --hard 版本号

3.5.3 查看以往版本号(本地的commit)

git reflog

3.5.4 查看各版本号及信息(所有的commit: 本地commit + 其他同事的commit)

git log

3.6 撤销修改

3.6.1 撤销修改

git checkout -- a.html

分两种情况分析: 还没有执行 git add 操作, 执行上面的操作后, 会恢复到和版本库中一模一样的版本状态。 执行了git add , 还没执行 git commit ,再执行上面的操作后, 会恢复到git add 结束后的状态 注: 一旦执行了git commit -m "*", 就不能再使用上面的命令回退。

3.6.2 对于已经push的版本, 进行回退

A、第一步:
git reset --hard 版本号 # 本地回退到指定的版本
B、第二步:
git push -f origin dev #将远程的也回退到指定版本

3.6.3 本地同步远程删除的分支

git fetch origin -p #用来清除已经没有远程信息的分支, 这样git branch -a 就不会拉取远程已经删除的分支了

3.6.4 删除掉没有与远程分支对应的本地分支

从gitlab上看不到的分支在本地可以通过git branch -a 查到, 删掉没有与远程分支对应的本地分支:

git fetch -p

3.6.5 查看远程库的一些信息, 及与本地分支的信息

git remote show origin

3.7 git stash临时保存本地操作

  • 1、使用 git stash 就可以将你当前未提交到本地(和服务器)的代码推入到Git的栈中, 这时候你的工作区间和上一次提交的内容是完全一样的。
  • 2、再切换到别的分支改紧急bug。
  • 3、改完后, 切到刚才的分支, 使用 git stash apply 将以前一半的工作应用回来。

也许有的人会说, 那我可不可以多次将未提交的代码压入到栈中?答案是可以的。当你多次使用 git stash 命令后, 你的栈里将充满了未提交的代码, 这时候你会对将哪个版本应用回来有些困惑, git stash list 命令可以将当前的Git栈信息打印出来, 你只需要将找到对应的版本号, 例如使用 git stash apply stash@{1} 就可以将你指定版本号为stash@{1}的工作取出来, 当你将所有的栈都应用回来的时候, 可以使用 git stash clear 来将栈清空。

在这里顺便提下 git format-patch-n , n是具体某个数字, 例如 git format-patch-1 这时便会根据log生成一个对应的补丁, 如果 git format-patch-2 那么便会生成2个补丁, 当然前提是你的log上有至少有两个记录。

3.8 常用的分支管理

3.8.1 正常合并流程

  1. 开发分支拉取流程: master -> release -> feature
  2. 提测和bugfix分支合并流程: feature(自测) -> feature/cdp_dev(接入团队额外的分支,开发集成测试) -> release(测试团队回归) -> develop(测试团队测试)
  3. Hotfix分支拉取合并流程: master -> hotfix -> develop(提测), hotfix -> master(上线)
  4. 上线分支合并流程: release -> master(正常上线),hotfix -> master(生产问题修复上线)
  5. 提测时开发提供的pr: release -> develop
  6. 上线时开发提供的pr: release -> master
  7. Hotfix开发提供的pr: hotfix -> master

3.8.2 分支冲突强制合并

  1. develop 拉取 feature_develop_diff
  2. master 合并到 feature_develop_diff 执行命令: git pull origin feature/feature_master 然后解决冲突,以master代码为主,提交代码
  3. 提PR: feature_develop_diff -> develop

3.9 子模块

git clone --recursive

# 或者执行git clone后再执行
git submodule update --init

4. 常用设置

4.1 用户设置

git config --global user.email "iawen@iawen.com"
git config --global user.name "iawen"

4.2 常用别名

git config --global alias.st status
git config --global alias.br branch
git config --global alias.co checkout
git config --global alias.ci commit
git config --global alias.unstage 'reset HEAD --'
git config --global alias.last 'log -1 HEAD'
git config --global alias.graph 'log --oneline --decorate --graph --all'

git config --global user.signingkey 0A46826A
git config --global core.autocrlf false # 忽略换行
git config --global core.saftcrlf true  # 拒绝提交包含混合换行符的文件

git config --global alias.dfw 'diff --ignore-space-change' # 忽略空白行
git config --global merge.renormalize true # merge时忽略行尾对比

4.3 保存密码

  • 每次操作都需要输入用户名和密码感觉很繁琐, 解决方法, 在本地的工程文件夹的.git下打开config文件, 添加:
[credential]
helper = store
  • 先用git拉一次东西, 拉的时候会提醒你输入帐号的密码。输入正确的帐号和密码后, 等东西拉完以后输入
git config --global credential.helper store

5. 常用方法

5.1 查看引用日志

git reflog

引用日志记录了最近几个月你的HEAD和分支引用所指向的历史。如果想查看仓库中HEAD在5次前所指向的提交, 可以使用@{n}来引用reflog 中输出的提交记录:

git show HEAD@{5}

也可以使用这个语法来查看某个分支在一定时间前的位置, 如:

git show master@{yesterday}

引用日志只存在于本地仓库, 一个记录你在你自己的仓库里做过什么的日志。

5.2 祖先引用

祖先引用是另一种指明提交的方式。如果你在引用的尾部加上一个^, Git会将其解析为该引用的上一个提交。

git show HEAD^
git show 973f552^2

另一种指明祖先提交的方法是~。同样是指向第一父提交, 因此HEAD~和HEAD^是等价的, 区别在于你在后面加数字的时候。HEAD~2代表“第一父提交的第一父提交”, 也就是祖父提交。

5.3 提交区间

最常用的指明提交区间语法是双点: (..)。如果你想看当前开发分支dev 是否落后于master分支, 可以使用:

git log dev..master
# 反之, 再查看当前开发分支dev 是否有未合并到master
git log master..dev
# 查看未推送到远端的内容: 
git log origin/master..HEAD

三点区间选择, 可以选择出被两个引用中的一个包含但又不被两者同时包含的提交:

git log master...dev
git log --left-right master...dev

–left-right选项, 显示出每个提交到底处于哪一侧的分支, 会让输出数据更加清晰。注: 结合–pretty=oneline 一起使用

5.4 交互式暂存

git add -i

5.5 储藏与清理

git stash  #或git stash save
git stash list
git stash apply #或 git stash apply stash@{2} 
#如果不指定, 则认定是最近的储藏
 
# 移除储藏: 
git stash drop stash@{0}   #或 git stash pop
 
git clean
# 被设计为从工作目录移除未被跟踪的文件。如果你改变语音, 你也不一定能找回来那些文件的内容。一个更案例的选项是运行: git stash --all 来移除每一样东西并存放在栈中。
git clean # 去除冗余文件或者清理工作目录(只会移除没有忽略的未跟踪文件)。
git clean -f -d # 移除工作目录中所有未追踪的文件以及空的子目录
git clean -d -n # 演示

5.6 日志搜索

git log -Sxxxx ---oneline
# 行日志搜索, 如查看zlib.c文件中git_deflate_bound函数的每一次变更: 
git log _L :git_deflate_bound:zlib.c
# 如果Git无法计算出如何匹配你代码中的函数和方法, 你可以提供一个正则表达式, 如: 
git log -L '/unsigned long git_deflate_bound/', /^}/:zlib.c

6. 使用Git 来实现持续集成(CI)

使用Git来完成代码发布, 主要是在服务端的hoot/post-receive 脚本, 下面是一个简单的示例:

#!/bin/bash

unset GIT_DIR   #很 重要

PROJECT_DIR=/data/website/note
TMP_CHANGE=/tmp/$$.log
LOG_FILE=/data/logs/note.log

cd $PROJECT_DIR
git add . -A && git stash
git pull origin master >> $TMP_CHANGE
MSG="`date +"[%Y-%m-%d %H:%M:%S]"` sync note content"

# 根据代码变动, 做相应处理
cat $TMP_CHANGE | grep ".go"
if [ $? -eq 0 ]
then
    killall mynote
    /usr/local/go/bin/go build -o mynote *.go
    nohup ${PROJECT_DIR}/mynote 2>&1 >> $LOG_FILE &
    MSG="$MSG Recomplie program"
else
    # do something
fi

echo $MSG >> $LOG_FILE
rm -rf $TMP_CHANGE

这里需要注意权限问题, 如果用户没有对发布目录的写入权限, 则会出错!!

7. 常用命令详解:

7.1 git log

常用的选项及其释义

选项 说明
-p 按补丁格式显示每个更新之间的差异
–word-diff 按 word diff 格式显示差异
–stat 显示每次更新的文件修改统计信息
–shortstat 只显示 –stat 中最后的行数修改添加移除统计
–name-only 仅在提交信息后显示已修改的文件清单
–name-status 显示新增、修改、删除的文件清单
–abbrev-commit 仅显示 SHA-1 的前几个字符, 而非所有的 40 个字符
–relative-date 使用较短的相对时间显示(比如, “2 weeks ago”)
–graph 显示 ASCII 图形表示的分支合并历史
–pretty 使用其他格式显示历史提交信息。可用的选项包括 oneline, short, full, fuller 和 format(后跟指定格式)
–oneline –pretty=oneline –abbrev-commit 的简化用法

最有意思的是 format, 可以定制要显示的记录格式, 这样的输出便于后期编程提取分析, 下表列出了常用的格式占位符写法及其代表的意义。

选项 说明
%H 提交对象(commit)的完整哈希字串
%h 提交对象的简短哈希字串
%T 树对象(tree)的完整哈希字串
%t 树对象的简短哈希字串
%P 父对象(parent)的完整哈希字串
%p 父对象的简短哈希字串
%an 作者(author)的名字
%aN mailmap的作者名字 (.mailmap对应, 详情参照git-shortlog(1)或者git-blame(1))
%ae 作者的电子邮件地址
%aE 作者邮箱 (.mailmap对应, 详情参照git-shortlog(1)或者git-blame(1))
%ad 作者修订日期(可以用 -date= 选项定制格式)
%aD 日期, RFC2822格式
%ar 作者修订日期, 按多久以前的方式显示
%at 日期, UNIX timestamp
%ai 日期, ISO 8601 格式
%cn 提交者(committer)的名字
%cN 提交者名字 (.mailmap对应, 详情参照git-shortlog(1)或者git-blame(1))
%ce 提交者的电子邮件地址
%cE 提交者 email (.mailmap对应, 详情参照git-shortlog(1)或者git-blame(1))
%cd 提交日期
%cD 提交日期, RFC2822格式
%cr 提交日期, 按多久以前的方式显示
%ct 提交日期, UNIX timestamp
%ci 提交日期, ISO 8601 格式
%d ref名称
%e encoding
%f sanitized subject line, suitable for a filename
%b commit信息内容
%s 提交说明
%N commit notes
%gD reflog selector, e.g., refs/stash@{1}
%gd shortened reflog selector, e.g., stash@{1}
%gs reflog subject
%Cred green
%Creset 重设颜色
%C(…) 制定颜色, as described in color.branch.* config option
%m left, right or boundary mark
%n 换行
%% a raw %
%x00 print a byte from a hex code
%w([[,[,]]]) switch line wrapping, like the -w option of git-shortlog(1)

示例:

git log --pretty=format:"%C(auto)%h%Creset - %an, %ar : %s" -5 --graph --all

8. 图解git flow开发流程

git flow的工作流程图: 0

8.1 Git Flow 的常用分支

  • 生产分支(master)‌ Master分支是仓库的主分支, 这个分支包含最近发布到生产环境的代码, 最近发布的Release, 这个分支只能从其他分支合并, 不能在这个分支直接修改‌

  • 补丁分支(hotfix)‌ 当我们在生产环境发现新的Bug时候, 我们需要基于master分支创建一个Hotfix分支, 然后在Hotfix分支上修复bug, 完成Hotfix后, 我们要把hotfix分支合并回Master和Develop分支‌

  • 发布分支(release)‌ 当你需要发布一个新功能的时候, 要基于Develop分支创建一个Release分支, 在Release分支测试并修复bug, 完成release后, 把release合并到master和develop分支‌

  • 开发分支(develop)‌ 这个分支是我们的主开发分支, 包含所有要发布到下一个Release的代码, 这个主要合并与其他分支, 比如Feature分支‌

  • 功能分支(feature)‌ feature分支主要是用来开发一个新的功能, 一旦开发完成, 我们合并回Develop分支进入下一个Release‌

8.2 git flow的具体使用细节

当我们新建git仓库之后, 默认会创建一个主分支也就是master分支, 由于master分支是用于发布生产环境, 所有必须保证master上代码的稳定性, 所以我们不能直接在master分支上修改提交。我们要基于master分支创建一个develop分支, develop分支用于保存开发好的相对稳定的功能, master分支和develop分支是仓库的常驻分支, 一直会保留在仓库中.

当新的开发任务来了之后, 就要编写代码了, 我们尽量不要在develop分支上写代码, 要保证develop分支的相对稳定, 所以这时我要就要基于develop 分支创建一个临时的开发分支, 然后在开发分支上编写代码, 等功能开发完之后我们再把开发分支合并到develop上.

新功能合并到develop分支之后, 我们想把新功能发布到生产环境, 首先基于develop分支创建release分支, 然后在release分支测试完成之后, 把release分别合并到master分支和develop分支.

release分支合并到master分支之后, 在master分支上打标签用于发布: 1

我们把代码发布到了生产环境, 用户在使用的时候给我们反馈了一个bug, 这时我们需要基于master分支创建一个hotfix 分支, 用于修复bug, bug修好之后, 把hotfix 分支分别合并到master分支和develop分支. 2

8.3 Git flow工具

如果理解了上面的流程, 完全可以不使用Git flow工具, 但是如果你想更标准化的执行git flow, 可以尝试使用git flow工具‌

# Mac OS X
$ brew install git-flow‌

# Linux
$ apt-get install git-flow

# Windows
$ wget -q -O - --no-check-certificate https://github.com/nvie/gitflow/raw/develop/contrib/gitflow-installer.sh | bash
  • 初始化: git flow init
  • 开始新Feature: git flow feature start MYFEATURE
  • Publish一个Feature(也就是push到远程): git flow feature publish MYFEATURE
  • 获取Publish的Feature: git flow feature pull origin MYFEATURE
  • 完成一个Feature: git flow feature finish MYFEATURE
  • 开始一个Release: git flow release start RELEASE [BASE]
  • Publish一个Release: git flow release publish RELEASE
  • 发布Release: git flow release finish RELEASE 别忘了git push --tags
  • 开始一个Hotfix: git flow hotfix start VERSION [BASENAME]
  • 发布一个Hotfix: git flow hotfix finish VERSION

9. 常见问题收集

9.1 RPC failed; curl 18 transfer closed with outstanding read data remaining

remote: Compressing objects: 100% (6/6), done.
error: RPC failed; curl 18 transfer closed with outstanding read data remaining
fatal: the remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed

A、原因1: 缓存区溢出

解决方法: 命令行输入git config http.postBuffer 524288000 执行上面命令如果依旧clone失败, 考虑可能

B、原因2: 网络下载速度缓慢

解决方法: 命令行输入

git config --global http.lowSpeedLimit 0
git config --global http.lowSpeedTime 999999

如果依旧clone失败, 则首先浅层clone, 然后更新远程库到本地

git clone --depth=1 http://gitlab.xxx.cn/yyy/zzz.git
git fetch --unshallow

9.2 OpenSSL SSL_read:SSL_ERROR_sysCALL

# 用压缩的方式进行下载(--global设置成了全局, 也可以不设)
git config --global --add core.compression -1

# 增大缓存大小
git config --global http.postBuffer 524288000 # 500MB
git config --global http.postBuffer 1048576000 # 1GB

# 如何1+2解决不了, 那么利用ssh进行下载试试
git clone git://github.com/XX/XXXX.git

# Disable sslVerify
git config --global http.sslVerify "false"

#如果还是不行, 选择Download ZIP吧, 谁用谁爽

9.3 git clone的速度比较慢, 于是找到了办法分享给大家:

git clone特别慢是因为github.global.ssl.fastly.net域名被限制了。 只要找到这个域名对应的ip地址, 然后在hosts文件中加上ip–>域名的映射, 刷新DNS缓存便可。

在网站 https://www.ipaddress.com/ 分别搜索:

github.global.ssl.fastly.net
github.com

得到ip后, 在hosts 绑定, 然后刷新DNS

9.3.1 githubusercontent、github加速镜像

加速地址一览

9.3.2 加速 clone

$ git config --global url."https://hub.fastgit.org".insteadOf https://github.com 
$ git clone https://github.com/kubernetes/kubernetes.git 

# 查看git配置信息 
$ git config --global --list 
# 取消设置 
$ git config --global --unset url.https://github.com/.insteadof 

9.3.3 加速 release

9.3.4 加速 raw

9.4 git: ‘remote-http’ is not a git command. See ‘git –help’

安装 libcurl-devel、curl-devel , 然后重新编译 git:

yum install -y libcurl-devel curl-devel