背景
jgit是Eclipse团队推出的一个使用java语言操作git命令的一个框架。在所有的Eclipse软件中封装了jgit工具,由于它的便捷与高效,因此开发团队将其单独剥离出来,成为一个可以供其他项目引用框架。
项目地址:eclipse.org/jgit/
问题解决
我们在Android 自定义的gradle插件项目中使用到了jgit框架。在使用jgit操作git push命令到远程时总会遇到remote hung up unexpectedly错误,网上查阅了一些资料都没有解决,最终在github中一个issue中找到了解决方案。可以直接拉到最后看解决方案
排查流程
先贴一下我的环境:
macOS 12.3.1
git version 2.30.1
Android Studio Arctic Fox | 2020.3.1
gradle :6.1.1
工程的build.gradle文件仅配置了jgit:org.eclipse.jgit:org.eclipse.jgit:5.11.0.202103091610-r,调用也是很简单的git.push().call()将当前的改动push到远程分支。
查了很多资料,得出问题产生的原因是由于本地git工程使用了ssh方式,jgit框架并不会解析本地秘钥导致推送操作错误,解析ssh要使用一个jsch库来配合使用才生效。添加相关依赖org.eclipse.jgit:org.eclipse.jgit.ssh.jsch:5.11.0.202103091610-r。
依赖添加完运行又提示秘钥失败 invalid privatekey: [B@6974f6a0 问题
添加了jsch后会报这个秘钥无效的问题,原因是本地解析秘钥时,读取了jgit设置的GIT_SSH环境变量,未读取到该变量导致解析失败。
当然网上也有其他的解决方案,比如降低openssh版本,将id_rsa秘钥文件转换为.ppk类型的秘钥文件,网上方案对我这个android工程并不适用。
Google上找到了一个类似的issue:github.com/alexvasilko… 彻底解决了这个问题。
原作者是这么解答的:
First of all, it turns out Jgit moved SSH support into separate libs as of version 5.8, so it have to be explicitly included, exactly as you pointed out. But just including the correct lib didn't work as Jgit still cannot find the correct
SshSessionFactory(seems like JavaServiceLoaderdoes not work well with Gradle plugins). Luckily it can be set manually.But it turned out that
org.eclipse.jgit.ssh.jschdoes not work well, somehow I got vague "Auth fail" errors even though the old version based on grgit worked just fine. I ended up copying grgit hack that manually checks forsshinPATHand setsGIT_SSHvariable for Jgit to use system command instead of own broken implementation.
翻译一下就是:
jgit从5.8版本将ssh单独抽出作为一个独立的lib,所以出现了remote hung up unexceptedly错误。但仅仅添加一个jsch依赖并不会解决后续问题,即使你添加一个正确的SshSessionFactory(就像java中的ServiceLoader在gradle插件中不会生效)
所以参考了grgit的做法:检查系统变量中的ssh,并且设置一个GIT_SSH变量供jgit使用系统命令。
解决方案
build.gradle中引入
implementation 'org.eclipse.jgit:org.eclipse.jgit:5.11.0.202103091610-r'
implementation 'org.eclipse.jgit:org.eclipse.jgit.ssh.jsch:5.11.0.202103091610-r'
在程序入口设置SshSessionFactory和SystemReader
fun checkGitEnvironment() {
if(isChecked){
//只设置一次就OK
return
}
SshSessionFactory.setInstance(JschConfigSessionFactory())
SystemReader.setInstance(MSshSystemReader())
isChecked = true
}
重写SystemReader,用来读取ssh和自定义的GIT_SSH,代码如下:
class MSshSystemReader : SystemReader() {
private val delegate: SystemReader = getInstance()
override fun getHostname(): String {
return delegate.hostname
}
override fun getenv(variable: String): String? {
val value = delegate.getenv(variable)
return if ("GIT_SSH" == variable && value == null) {
findExecutable("ssh") ?: findExecutable("plink") ?: ""
} else {
value
}
}
override fun getProperty(key: String): String? {
return delegate.getProperty(key)
}
override fun openJGitConfig(parent: Config?, fs: FS): FileBasedConfig {
return delegate.openJGitConfig(parent, fs)
}
override fun openUserConfig(parent: Config?, fs: FS): FileBasedConfig {
return delegate.openUserConfig(parent, fs)
}
override fun openSystemConfig(parent: Config?, fs: FS): FileBasedConfig {
return delegate.openSystemConfig(parent, fs)
}
override fun getCurrentTime(): Long {
return delegate.currentTime
}
override fun getTimezone(zone: Long): Int {
return delegate.getTimezone(zone)
}
companion object {
private val PATH_SPLITTER = Pattern.compile(Pattern.quote(File.pathSeparator))
private fun findExecutable(exe: String): String? {
val path = System.getenv("PATH")
val envPaths = PATH_SPLITTER.split(path)
val pathWithExt: List<String> = if (envPaths.isEmpty()) {
listOf(exe)
} else {
envPaths.map { e ->
e + File.separator + exe
}
}
return pathWithExt.find {
Files.isExecutable(Paths.get(it))
}
}
}
}
这样就完美解决这个jgit推送失败的问题了。
参考文档: