Git flow与常见问题

单独描述一下开发中Git可能遇到的比较棘手或者疑惑的地方,大家可以直接跳到各自需要的地方查看,工具原则是基本了解后,用什么看什么 。文拆分自IDEA开发环境

更新1: 查漏补缺建议先看看猴子也会的git-高级篇, 建议结合下面的实际操作多试试, 纯看的确效率低也很容易忘.

更新2: git的架构和原理分析是单独的篇章, 暂时没时间深入研究, 可参考这篇git基本架构欣赏

0x01.Github相关

很常规的问题直接参考github官方文档. Gitlab相关单独说(注意基本概念,git仓库有本地跟远端两个)

1. Github PR (推荐Github官方客户端)

参与开源的第一步就是fork 开源项目,然后提交pr(pull request)了. 那么这里有个很经典的问题,就是原本你fork了代码之后是不会自动更新的,需要手动同步/merge一下.

更新补充: 如果安装github客户端可以一键更新同步, 不管是merge还是rebase ,都很方便 ,但是要注意rebase 的时候, 先把冲突解决了, 别保留双方冲突然后再去解决, 那样会重新算修改历史..

不依赖软件的步骤如下:

  1. down代码git clone git@github.com:xxx/yourusername/x.git (注意这里地址就是自己fork后的仓库地址)
  2. (可选)git remote -v 查看状态(会有一个你fork地址的fetch&push,执行步骤3后可再对比看看).
  3. 添加remote库 :git remote add xx https://github.com/xxxx/autorName/x.git (这里xx是指的对方版本的名字,随便取你觉得好的,地址就是对方仓库地址)
  4. git fetch xx 拉分支到本地 (可以看到* [new branch] master -> up/master 提示)
  5. git rebase xx/master 合并操作(如果无更新也会提示。也可用merge,具体区别待补充)
    • 4和5步可以合并为一个命令, git pull --rebase xx master (推荐)
  6. (可选)然后走git add-->git commit-->git push -f (注意rebase和amend操作需要强制推送, 否则会要求merge)
  7. 最后在你fork后的项目界面手动提PR,等待原项目作者确认(在一次PR确认期内可多次push,PR合并的时候可只选取一条提交记录)

第一次初始化好了之后,以后直接跳到第4步,之前不用重复做,具体如下:

先每次git remote update xx 获取更新(xx是3中设置的名字).然后rebase合并(如果有多个分支注意checkout 切换)

更新: 因为发现github的客户端做这些操作都是一键的, 并且方便查看对比, 所以理解了步骤之后, 建议还是安装一下github 客户端, 一键的更新, 一键的rebase 吧.

2. 快速更新

上面讲的是正常的PR/更新流程, 这里说一些更简单粗暴的.. 很多时候我们fork的项目更新了, 其实我们本地是毫无修改的, 按照原本的流程很可能会遇到冲突需要解决, 在确定我们需要覆盖的前提下, 我们有更直接的做法:

1
2
3
4
5
6
7
8
#1.更新全部远端分支
git fetch --all

#2.强制把远端分支覆盖到本地 (比如你当前在dev分支)
git reset --hard upstream/dev

#3.强制推送到fork的远端,这三板斧真是舒服了...
git push -f origin

当然这个做法有没有什么坑我目前还不清楚… 反正还挺好用的, 很舒服

3. 快速清理本地多余分支

很多时候, 远端的仓库会经常删除开发的分支, 此时你只是git pull 并不会更新本地的其它不存在的分支, 那你也不想手动去一个个删, 实际应该这样来做:

1
2
3
4
5
6
7
8
#检测有哪些分支远端已经不存在了.dry-run只是告诉你, 不会实际执行
git remote prune github --dry-run

#确认无误, 直接执行,就自动清理了
git remote prune github

#如果还有无效的远端分支, 那可以批量一次删除
git branch -d br1 br2 br3 ... brn

很多时候, github 会频繁涉及到rebase , 或者修改错误提交的相关问题, 这也是git进阶一个很重要/实用的类别 — “回滚/撤回“ , 详细参考后文的[tips](#0x03.Git Tips)

0x02.Git Flow

整个git glow应该是需要建立在多分支,多版本(tag),多人合作的前提下.那么先说说原则,再说说最常用的多分支跟tag.

A.原则

  1. master分支不开发,开发只在dev/个人分支,然后合并到master.
  2. master分支必须打tag
B.核心步骤
  1. 如果需要下载的代码不是主分支,使用git clone -b branchName https://xxx 即可.不需要下master
  2. 创建新的分支(比如dev) : git checkout -b dev (等同于先git branch dev 再git checkout dev)
  3. 然后git add –>commit ,然后有两种选择,一是直接push到远端git仓库,二是合并到master
    • 推送dev分支,git push dev (当然第一次可能需要带上--set-upstream )
    • 合并dev到master : git checkout master ,git merge --no-ff dev ,这时候也有两种情况,冲突否?
      1. 冲突,人工解决冲突文件,删掉冲突标记.然后再add->commit->push
      2. 不冲突,fast-forward
  4. 当从git仓库clone代码的时候,git就会自动把本地的master分支和仓库端对应起来,仓库端默认名是origin ,git remote -v 可以查看详细信息. (正常权限会有fetch 和 push [拉取])

C.想把master内容合并到其他分支

大部分流程说的都是如何把dev/其他分支合并到master,但是实际开发中还有个常见需求,就是不希望影响master分支,但是希望合并到自己的分支,master–>dev ,步骤如下:

  1. (可选)如果dev有改动未提交,先提交改动,不然会禁止切到master
  2. git checkout master
  3. git pull
  4. (可选) 如有冲突,这里需先解决,然后 git add. –> git commit -m "merge master to dev"
  5. git checkout dev
  6. git merge master (这一步是核心,把master改动合并过来,在master如果没提交这会自动补充msg)
  7. git push (推送到远程仓库的dev)

X.撤销提交和回滚

核心是reset命令, 但是千万要注意特别情况下的备份和处理, 不然reset一时爽….

Y.什么是Cherry-pick

首先这是一个单独的命令git cherry-pick,用到的地方可能不会很多,但是需要了解。简单说,它可以操作某一个分支合并其它分支的一次提交, 比如我现在使用的是稳定版的分支stable , 我需要从dev 分支上合并一个新功能, 那么就很适合使用cherry-pick 命令.

0x03.Git Tips

1. 回滚/撤回

这个涉及到大量生产环境修改git log / 提交历史, 文件回滚等操作, 比较复杂且很有些危险, 觉得应该单独开一篇文章来讲, 不过因为我接触的也不全, 就先说说比较简单/常见的案例吧.

1.1 错误的add提交

因为大家习惯使用git add . 来添加所有变动文件, 一不小心就容易把不该加的文件添加进去了, 这个时候如果还没有commit , 那么可以这样来操作删掉添加的错误文件

1
2
#--cached只会从缓存区中移除文件,不会从磁盘移除
git rm --cached fileName # 可以从git status 看到

如果已经提交了, 不管是在本地还是已经push了, 最简单的做法其实都是revert commitId 了, 所以尽量别到这样…

1.2 错误的邮箱和用户名

在内部和github之间, 你很可能有两套git用户, 比如gitlab & github , 在PR和本地提交之间很容易没注意写错了邮箱, 如果你发现及时, 这是你最近一次提交, 那可以这样操作:

1
2
3
git commit --amend --author="usrname <mail>" 
#如果你已经一步到位,push到了git服务器端,那么你会发现不能直接push,需要force
git push -f #默认是origin,如非自己补充.

然后你最好给你需要设置不同用户名的项目单独设置一个用户名+邮箱 (和全局不冲突):

1
2
git config user.name "xx"
git config user.email "xx"

这样就可以愉快的在多个项目间自由使用不同的邮箱+用户名了 ,也不怕搞错了.

1.3 提交信息commit-log

先说简单的, 大家常用的是git commit -m "msg" 来记录这次提交的信息, 有时候需要填写一段内容, 其实最简单的就是直接git commit , 它会默认打开一个文本编辑框, 你可以输入信息, 注意#代表此行是不显示的.

然后

在执行commit操作的时候, 一般至少会带上一个基本信息, 如果这个信息写错了, 但是你已经push到了server端呢? 分两种情况

  1. 提交信息少了, 我想补充一点 (简单) : git commit -a --amend ,这样不会重新开启一个commit ,而是整合到一个. (如果已经提交了多个分支, 那么在本地先reset到第一个, 然后重新push -f 覆盖.)
  2. 提交信息错误, 我希望覆盖新的信息 :
1.4 错误的分支名

有些时候开发就需要定一个新的分支名, 但是很可能后面觉得名字不太合适, 就需要修改, 如果分支还没有提交到远端, 那很简单 : git branch -m oldName newName 就行了, 如果和远端不一致的话呢~

1
2
3
4
5
6
7
8
9
10
11
#1.先修改本地名字
git branch -m oldName newName

#2.删除远程已有的分支
git push --delete origin oldName

#3.推送新的本地分支
git push origin newName

#4.修改后的分支与本地关联(可选)
git branch --set-upstream-to origin/newName
1.5 错误的rebase/merge等操作

这里也是比较棘手的. 首先, 推荐多用rebase命令, 少用pull/merge(pull自动触发merge, 所以也慎重), 下面以rebase为例, 讲一下相关的撤回操作.

  1. 如果是在rebase生效的途中, 提示你被中断, 需要解决冲突, 然后你后悔了, 你可以很简单的通过git rebase --abort 取消回滚.

  2. 如果是rebase已经完全生效, 那么目前没有直接的回滚undo 命令, 都需要使用reset 这种危险操作. (注意备份)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 1.通过reflog这个强大的命令, 查看所有的操作过程
    git reflog

    # 2.强制reset回某个时间点
    # 这里使用soft的效果就是先只回滚指针,而没有变更数据.需要手动再处理一下暂存区的数据(丢弃/添加), 安全一些.
    git reset --soft HEAD@{id} # 这里id是具体的数,别看错了..

    # 2.1 慎重
    git reset --hard HEAD@{id} # 如果熟悉确定, 可以直接reset --hard一次到位...建议提前备份:)

Git的回滚整个生效的原理, 一般也都是基于这整个的记录, 所以遇到错误提交, 先别慌张.. 只要没做一些把日志记录也一并误删的情况, 都是有救的. 不过建议养成危险操作先备份的习惯.

2. 快捷别名(推荐)

配置alias(别名). 跟bash一样,git有些常用命令也比较冗长,设置一些快捷名是很有裨益的.git config --global alias.cm commit (语法) .以下是我常用的缩写

  • ck checkout
  • ps push
  • br “branch -a” #注意多个参数需要用双引号括起来,否则只会读第一个
  • rmt “remote -v”
  • st status
  • lg “log –graph -4” #显示最近4次的log,以及gitflow图
  • mg “merge –no-ff” #保留合并,推荐

然后就可以直接git br显示所有分支了. 当然这每次还要输入git,能不能把git也省了呢,也可以做成一个文件存下来, 方面服务器上同步.

3. Tag的使用

打tag是很简单的事情,git tag 1.0 代表在当前分支打上了和commit对应的一个标签(因为commit好比是ip,很难记忆,而tag好比是域名,做了一层映射.) ,如果想查看tag信息 git show 1.0 即可

4. 创建新的空分支

默认情况下, 你直接git checkout -b newBranch , 会同时得到所有的当前分支的文件和提交记录, 而有些时候, 我们需要特定分支单独用于其他场景, 比如备份/文档等等. 这时我们就需要学会单独创建一个新的空分支, 然后清空数据了.

1
2
3
4
5
#1.使用--orphan参数, 详情可参考help/man
git checkout --orphan newBranch #注意不需要提前创建, 也就不能带-b参数了.

#2.清空之前的文件, 瞬间干净, 优雅安全~
git rm -rf .

0x04.Git相关问题

1. github 无法push 22port(卡在debug: pledge: network )

这个问题非常蛋疼,google查的也都是不对的…关键核心原因应该还是网络策略的屏蔽跟阻断.测试方法很简单,如果是公网那么无线换成自己的手机热点,如果正常说明是网络限制,那么在~/.ssh/config (需要自己新建)

1
2
3
4
5
Host github
Hostname ssh.github.com
Port 443
User git
IdentityFile ~/.ssh/github

~/.ssh/config 文件加入上述即可…可以解决22端口connection xxx ,以及网络模块卡住的问题.然后如果是多人合作,你想通过ssh访问他人仓库,原本应该是有权限之后,这样去访问就行 :git clone git@github.com:other/xx.git ,但是你忘记了这样又走回了22端口,所以应该是git clone github:other/xxx.git

最后提醒,如果因为各种原因导致配置文件无法正确识别,终极大法直接去修改.git/config 文件.如果以上方法都没解决,尝试一下卡着的地方能不能输入命令如ls ,参考问题3.以及最新更新

补充: 但是,如果是纯内网环境下,默认git仓库比如gogs是没有开启https的. 所以需要手动去配置https然后进行尝试.但是hostname如何填写待确定. (可以直接填写ip)

2. git 提交成功不显示绿色(Hexo多见..)

直接说原因…是因为git的user.email配置与github的邮箱不匹配. 你可以选择修改本地的默认邮箱,也可以在github的用户邮箱设置里面添加上你本地配置的.本地查看配置git config --list,修改命令是 :

git config --global user.email "youremail@xx.xx" . Github的QA说的都不对…不过考虑到这个问题常见于hexo,因为hexo部署的时候不会显示邮箱了,如果自己add->commit->push是容易发现一点的.

不过..切换回了之后之前丢失的绿色是不会补上的~ BTW,如果不是master分支也不会显示的,必须合并到master或者默认就是其他分支才行.

3. Git clone显示filename too long

这个可以用一行设置解决, 全局设置:

1
git config --global core.longpaths ture

n. Hexo部署时提示The remote end hung up unexpectedly

这个问题也异常蛋疼,因为时好时坏,且报错还偶尔变化(参考过官方issue)。不过发现麻烦问题后,首要还是确定根本原因先 (建议先控制变量,把能对比测试排除的先排除,后续这部分我重新整理一下)。

如果hexo报错之前已经提示Connection reset by xx.xx.xx.xx port 22 ,说明你git都过不去了,那先尝试ssh -vT hexo 看看debug信息以及卡在了哪里(注意这个hexo是我在config配置的别名,默认x@x.x

然后我发现它验证Authentication succeeded ,然后又卡在debug1: pledge: network 这个鬼地方了。。。那还是参考1.的办法,发现仍然卡住(因为这已经是国内网了)

然后尝试排除变量,试试github的ssh测试ssh -vT github ,发现通了,那说明不是git本身问题,多可能是git服务器端出现问题。 (类似docker误删了什么?后续再补充。。)

  • 考虑git版本 or 默认cmder走的OpenSSH版本问题,因为头部显示还是OpenSSH_for_Windows_7.6p1, LibreSSL 2.6.4

  • 考虑hexo自身哪出了问题,因为有时候hexo clean后可正常

  • 考虑服务器git/SSH那边问题,看看日志

    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
    #1.服务器端测试临时ssh端口
    /usr/sbin/sshd -d -p 7777
    #2.在你的机器测试去连接
    ssh -vT hexo -p 7777
    #然后就可以看见两边都有详细信息了,这是失败的时候,会卡在这个环境信息输出之后
    Environment:
    USER=root
    LOGNAME=root
    HOME=/root
    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
    MAIL=/var/mail/root
    SHELL=/bin/bash
    SSH_CLIENT=yourIP 64876 7777
    SSH_CONNECTION=yourIP 64876 10.104.145.59 7777
    SSH_TTY=/dev/pts/1
    TERM=cygwin
    XDG_SESSION_ID=7785
    XDG_RUNTIME_DIR=/run/user/0
    #这个时候我尝试随便输入命令,意外发现居然可以执行...(见下图),然后last查看发现只有瞬时登陆

    #更新查看日志变化.同时试着SSH连接看看状态
    tail -f /var/log/message #或者secure文件

    #可以尝试重启systemd登录模块重启
    $ systemctl restart systemd-logind

    sshError00

6.6:更新:问题仍然未解决,就是时好时坏,之后抽空再重新看看git服务器的日志

6.12更新: 今天还是连不上,但是有个重大突破. 发现用ssh -vvv hexo 命令,可以进去. 而且百试百灵.但是用ssh -vT 或者普通的ssh连接都会一直卡在pledge network. 原因待后续有空继续排查.(比如vvv到底做了什么)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#再更新.ssh相关参数
-T #Diasble pseudo-tty allocation
-t #force pseudo-tty allocation.
-v #本质是开启debug模式打印信息,最多3个v,只是打印信息的详细程度不同.

#然后我发现,直接ssh host 或者ssh -t 或者ssh -v 都是直接成功登录了.
#就是ssh -T卡住. 然后xshell和hexo d 也是失败的. 真是邪门了....

#解决方案在 .deploy_git/config 最后加入
[http]
postBuffer = 524288000

#直接命令也可以添加以下三. 特别是网络经常不好的时候...
git config --global http.postBuffer 524288000
git config --global http.lowSpeedLimit 0
git config --global http.lowSpeedTime 99999 #海外VPS

#如果上面的方式失效
/usr/local/nginx/sbin/nginx -s stop #然后启动...

6.13更新: 今天终于搞好了,貌似找到了hexo deploy失败的原因跟解决方案了. 分析见参考文章. 本质是Nginx和本地git直接发送请求的时候buffer过小导致出了问题.

4. Hexo生成文件提示大量LF will be replaced by CRLF

每次hexo g -d 出现大量的上述提示很扰人, 两个方法解决这个问题:

  1. 在编辑器里设置保存的时候默认为CRLF (当然建议统一为LF)
  2. 根本上git config --global core.autocrlf false ,这等于是关闭了提示..

参考文章: