删除git无用分支

平常我们都会有自己的工程,时间长了后,比如2-3年后,repo里一般都会充满了各种各样的分支,有很多其实已经没用了,典型的如已经合并到主干的分支,但目前的现状一般是没人理会这些分支,如下:
avatar

大家放任不管,主要还是因为有点麻烦,需要人工识别是否还需要,然后再一个个删除,非常琐碎。因为吃力不讨好,所以没人愿意干,既然没人愿意干,那么有没有更加高级的,通过机器的方式自动帮我们做呢?答案是肯定的。接下来,我们将通过git命令+groovy来打造一个牛逼的脚本工具,自动帮我们做这件事情。

问题分解
首先我们的目标是:清理远端不需要的分支。
这里我们需要明确下不需要的含义,对应到git中,我们将其定义为merge到主干的分支,比如已经merge到master的分支(这里我们以master作为主干),我们有如下命令帮我们筛选出目标分支,如下:
git branch -r --merged origin/master

其次,我们还需要考虑下时间因素,比如我们期望是已合并到主干且时间超过一个月的(我们比较保守哈,像那种最近一周刚刚合并到主干的分支,我们暂且不删除,免得又需要了),那么我们需要知道某个分支最近一条commit的提交时间,还好我们有如下命令可以获取:
git show -s --format=%ct $commitId (注意返回的结果以秒为单位哦)
有了这个值,通过和当前时间戳做比较,我们就能得到间隔时长了。

通过上面的步骤,我们就可以初步过滤出所有已合并到主干且时长超过一个月的远程分支了,接下来就是愉快的删除之了。

完整代码如下(delmergedbranches.groovy):

#!/usr/bin/env groovy
def file = new File(".git")
if (!file.exists()) {
    println "not a git repo, ignore!!!"
    return
}

// 开始清理分支之前,先把远端处于stale状态的分支清理下,通过下面的命令
// 可通过git remote show origin查看状态
// 先清理后,也可以保证下面的'git branch -r --merged'命名返回是精确的,比如那些远端实际已经删除了的分支就不会返回了
println "before del branches, run remote prune first..."
"git remote prune origin".execute()

// 平时开发的主干分支
def mainline = "master"
// 一个月毫秒值,默认分支清理时长间隔设置
def timeSpan = 30 * 24 * 60 * 60 * 1000L
// release分支设置,采用默认分支的3倍时长,3个月即差不多保留最近的6个版本
def timeSpanForRelease = 3 * timeSpan

// 相对远端的master分支哈,输出已经合并到远端master的所有远端分支
def allMergedBranches = "git branch -r --merged origin/$mainline"
def proc = allMergedBranches.execute()
"""
输出如下:
  origin/HEAD -> origin/master
  origin/dev/3.0.8
  origin/dev/3.1.10
  origin/dev_xiaoweiz_211206_support_64
  origin/feat/ble_beacon_return_edu
  origin/feat/helmet_face
  origin/master
  origin/master_v3418
  origin/release/3.1.0
  origin/release/3.1.10
  origin/release/3.5.6
  origin/release/3.5.8
  origin/release/mvp
  origin/tr_test_3.4.14
  origin/tr_test_h5
"""

def allMerged = []
// refer to: https://groovy-lang.org/groovy-dev-kit.html#process-management
// proc.in corresponds to an input stream to the standard output of the command.
// proc.out will refer to a stream where you can send data to the process (its standard input).
proc.in.eachLine { line ->
    // 按行处理命令的输出
    if (!line.contains(" -> ")) {
        // 排除掉origin/HEAD -> origin/master这一行
        allMerged << line.trim()
    }
}
if (!allMerged) {
    println "nothing need to do1!!!"
    return
}
println "allMerged size===${allMerged.size()}"
println allMerged

// 合并到master且最新一条commit时间距离现在超过1个月的
def toDels = []
long currentTimeInMs = System.currentTimeMillis()
allMerged.each {
    long commitMs = getCommitTimeInMs(it)
//    println "currentTime===$currentTimeInMs, commitMs=$commitMs, cid=$it"
    long span = it.startsWith("origin/release/") ? timeSpanForRelease : timeSpan
    if (currentTimeInMs - commitMs > span) {
        toDels << it
    }
}
if (!toDels) {
    println "nothing need to do3!!!"
    return
}
//println "toDels size===${toDels.size()}"
//println toDels

def realDels = []
toDels.each { String commit ->
    if (commit.startsWith("origin/")) {
        // 去掉开头的origin/前缀
        def branch = commit.substring(7)
        boolean del = needDel(branch)
        println "before real del, branch===$branch, del=$del"
        if (del) {
            realDels << branch
        }
    }
}
if (!realDels) {
    println "no branches need to delete!!!"
    return
}

println "realDels size===${realDels.size()}"
println realDels

def input = System.console()?.readLine 'real del (y/n)? '
if ("y" == input) {
    realDels.each {
        realDelBranch(it)
    }
} else {
    println "You gived up!!!"
}

static def getCommitTimeInMs(String commitId) {
    String second = "git show -s --format=%ct $commitId".execute().text.trim()
    // 有可能为空,这里保护处理下
    second ? (second as long) * 1000 : System.currentTimeMillis()
}

// 我们要定期删除的分支特点,大家平时只要按这些规则建分支即可
static def needDel(String branch) {
    return branch.startsWith("dev/") || branch.startsWith("feat/") || branch.startsWith("dev_") ||
            branch.startsWith("release/")
}

static def realDelBranch(String branch) {
    // del local first
    "git branch -D $branch".execute()
    // then del remote
    "git push origin --delete $branch".execute()
    println "$branch, del done"
}

有没有点小心动,最后让我们来看下如何使用它。

如何使用
1、下载并安装groovy命令,并配置成命令行可见
下载:https://groovy.apache.org/download.html
这里我选择的是2.5.15的binary包,下载后解压到本地的某个目录;

2、配置路径
编辑home目录下的.bash_profile文件,添加如下内容:
export GROOVY_HOME=/Users/didi/dev/groovy-2.5.15-bin (上一步解压缩的目录)
export PATH= G R O O V Y H O M E / b i n : GROOVY_HOME/bin: GROOVYHOME/bin:PATH

3、新开一个terminal窗口,执行:
source .bash_profile
然后执行groovy -v,如果没问题的话,会有如下输出:
avatar

4、复制并保存上面的脚本代码
假如你是存放在~/groovy/目录下面,同时为了方便后续你在任意一个git repo里快速访问此脚本,你需要像上面那样将这个目录也添加到PATH变量里,如下:
export PATH= G R O O V Y H O M E / b i n :   / g r o o v y : GROOVY_HOME/bin:~/groovy: GROOVYHOME/bin: /groovy:PATH
这样设置后(需要再次执行source .bash_profile),你还需要给脚本添加个可执行权限,如下:
chmod a+x delmergedbranches.groovy
甚至你可以把脚本的后缀名去掉,直接叫delmergedbranches也行。

5、在项目中尝试
命令行进入到某个项目的根目录下,输入delmergedbranches(前面的命令行环境配置好的话,按Tab键会自动补全哦,脚本在真正删除之前会让你二次确认的,请放心尝试哦)。

注:本脚本是针对我们项目打造的,比如里面定义的主干是master,时长间隔是1个月,另外需要删除的分支,我们也有个前缀的判断(比如dev/、feat/),可能这些未必适合你的项目,可以简单修改下代码来满足你的需求。

最后关于定时任务,mac上我们使用crontab来实现,比如这里我们可以配置脚本每个月1号中午11点执行一次。
命令行执行crontab -e,输入如下内容:
0 11 1 * * ~/dev/groovy-2.5.15-bin/bin/groovy ~/groovy/delmergedbranches,
保存退出即可。
有时候你可能想知道脚本有没有执行或者执行过程有没有报错之类的,可以使用下面的命令:
0 11 1 * * ~/dev/groovy-2.5.15-bin/bin/groovy ~/groovy/delmergedbranches >> ~/crontest.log 2>&1,
这样你就可以在crontest.log文件里看见脚本的执行日志了,还是非常有用的。

参考链接
https://crontab.guru/#0_11_1__
http://groovy-lang.org/index.html
https://groovy-lang.org/groovy-dev-kit.html#process-management
https://medium.com/macoclock/automate-running-a-script-using-crontab-on-macos-88a378e0aeac
https://stackoverflow.com/questions/2003505/how-do-i-delete-a-git-branch-locally-and-remotely

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值