【问题标题】:Using forked package import in Go在 Go 中使用分叉包导入
【发布时间】:2025-12-04 17:05:02
【问题描述】:

假设您有一个位于 github.com/someone/repo 的存储库,并且您将其 fork 到 github.com/you/repo。你想使用你的 fork 而不是主 repo,所以你做一个

go get github.com/you/repo

现在此 repo 中的所有导入路径都将被“损坏”,这意味着,如果存储库中有多个包通过绝对 URL 相互引用,它们将引用源,而不是 fork。

有没有更好的方法将其手动克隆到正确的路径中?

git clone git@github.com:you/repo.git $GOPATH/src/github.com/someone/repo

【问题讨论】:

  • No 新分叉中的导入路径将被破坏,在分叉之前尚未破坏。
  • 很抱歉让您失望了,但事实并非如此。如果通过绝对 url 在导入中引用子包,则此导入将在 fork 中中断(或至少引用错误的包)。
  • 例如goamz。它到处都有内部引用。
  • 查看 ec2 包 - 它有一个 launchpad.net/goamz/aws 导入。 awsec2 包都位于相同的存储库中,因此在分叉时,不会引用正确的包(分叉中的那个)。
  • fork 将引用与 fork 源相同的包。那有什么不正确的? fork 会编译,它会构建,它会做和以前一样的事情。那么“错误包装”的定义是什么?请注意,Go 语言,以及它的构建系统,没有知道存储库,只有包。

标签: go


【解决方案1】:

如果您使用的是go modules。你可以使用replace 指令

replace 指令允许您提供另一个导入路径,该路径可能 是位于 VCS(GitHub 或其他地方)或您的 具有相对或绝对文件路径的本地文件系统。新进口 使用来自replace 指令的路径,无需更新 在实际源代码中导入路径。

所以您可以在 go.mod 文件中执行以下操作

module github.com/yogeshlonkar/openapi-to-postman

go 1.12

require (
    github.com/someone/repo v1.20.0
)

replace github.com/someone/repo => github.com/you/repo v3.2.1

v3.2.1 是你的仓库中的标签。也可以通过 CLI 完成

go mod edit -replace="github.com/someone/repo@v0.0.0=github.com/you/repo@v1.1.1"

【讨论】:

  • 效果很好。我认为这没有更多支持的唯一原因是人们还没有使用 go 模块。我还使用此技巧将文件位置指向工作站上的另一个目录,在该目录中我正在进行本地编辑。一旦我在 github 中推送我的本地编辑,我就会删除我的“替换”行。
  • ^ 100% 同意。投票给人们。
  • 哦,但是“大师”对我不起作用。我必须在那里编写 v0.0.1 或某些特定版本。
  • 你也可以直接在命令行go mod edit -replacego mod edit -replace="github.com/someone/repo@v0.0.0=github.com/you/repo@v1.1.1"@v... 都是可选的。
  • 有一个go.mod.localgo.mod.dev 的作用是实际替换本地开发的导入路径不是很酷吗?我的意思是,您永远不会忘记删除丑陋的“替换”,因为您不必这样做。
【解决方案2】:

处理拉取请求

  • fork 一个仓库github.com/someone/repogithub.com/you/repo
  • 下载原码:go get github.com/someone/repo
  • 在那里:cd "$(go env GOPATH)/src"/github.com/someone/repo
  • 启用上传到您的分叉:git remote add myfork https://github.com/you/repo.git
  • 将您的更改上传到您的仓库:git push myfork

http://blog.campoy.cat/2014/03/github-and-go-forking-pull-requests-and.html

在项目中使用包

https://github.com/golang/go/wiki/PackageManagementTools

【讨论】:

  • 我应该从哪个文件夹git remote add?从叉子克隆?从原始克隆?从内部去?
  • @lapots 在原始仓库(即 $GOPATH/src/github.com/somone/repo)中运行命令
  • 如果我想对很久以前分叉的仓库添加更改怎么办?
【解决方案3】:

解决它的一种方法是 Ivan Rave 和 http://blog.campoy.cat/2014/03/github-and-go-forking-pull-requests-and.html 建议的方法——分叉的方式。

另一种方法是解决 golang 行为。当您go get 时,golang 会以与存储库 URI 中相同的名称来布置您的目录,这就是麻烦开始的地方。

如果您发出自己的git clone,则可以将您的存储库克隆到以原始存储库命名的路径上的文件系统上。

假设原始存储库位于 github.com/awsome-org/tool 并且您将其 fork 到 github.com/awesome-you/tool,您可以:

cd $GOPATH
mkdir -p {src,bin,pkg}
mkdir -p src/github.com/awesome-org/
cd src/github.com/awesome-org/
git clone git@github.com:awesome-you/tool.git # OR: git clone https://github.com/awesome-you/tool.git
cd tool/
go get ./...

golang 非常乐意继续使用此存储库,并且实际上并不关心某些上层目录的名称为 awesome-org 而 git 远程是 awesome-youawesome-org 的所有导入都通过您刚刚创建的目录进行解析,该目录是您的本地工作集。

更多篇幅请看我的博文:Forking Golang repositories on GitHub and managing the import path

编辑:固定目录路径

【讨论】:

  • 我同意这是解决此问题的“最佳”解决方案。但是很高兴看到人们在 Docker 容器中运行 Go 应用程序时如何管理这个工作流。我正在学习 golang,并想在我正在使用的库中添加一个小功能,当我在创建拉取请求之前对它进行测试时遇到了这个头痛。
【解决方案4】:

如果您的分叉只是临时的(即您打算将其合并),那么只需在原地进行开发,例如在$GOPATH/src/launchpad.net/goamz

然后您使用版本控制系统的功能(例如git remote)将上游存储库而不是原始存储库设为您的存储库。

通过go get 使其他人更难使用您的存储库,但更容易将其集成到上游。

事实上,我在 lp:~nick-craig-wood/goamz/goamz 有一个 goamz 存储库,我正是以这种方式开发的。也许有一天作者会合并它!

【讨论】:

  • 所以我理解这样做的含义,如果我走这条路,当有人从我的仓库中执行go get 时,我所有的导入语句等仍将反映github.com/original_author,因此被打破......正确吗?
  • @parker.sikand 是的,这是正确的。这种技术最适合您打算在上游合并的东西,而不是 go get 使用。如果您打算永久分叉软件包,请使用其他答案的技术。
【解决方案5】:

这是一种适用于所有人的方法:

使用 github fork 到“my/repo”(只是一个例子):

go get github.com/my/repo
cd ~/go/src/github.com/my/repo
git branch enhancement
rm -rf .
go get github.com/golang/tools/cmd/gomvpkg/…
gomvpkg <<oldrepo>> ~/go/src/github.com/my/repo
git commit

每次使代码更好时重复:

git commit
git checkout enhancement
git cherry-pick <<commit_id>>
git checkout master

为什么?这使您可以拥有任何go get 都可以使用的存储库。它还允许您维护和增强对拉取请求有益的分支。它不会用“供应商”使 git 膨胀,它保留了历史,并且构建工具可以理解它。

【讨论】:

【解决方案6】:

这个问题的答案是,如果你 fork 一个包含多个包的 repo,你将需要重命名所有相关的导入路径。这在很大程度上是一件好事,因为您已经分叉了所有这些包,并且导入路径应该反映这一点。

【讨论】:

  • 在我对 Go 项目的第一次贡献中,我花费的时间比我愿意承认诊断出的时间还要多。 “所有测试都通过了,包括我为详尽测试新功能而编写的测试。怎么了?!”您是否知道任何可用的工具来减轻初学者的这个绊脚石?
  • 一旦我发现它很容易使用findxargssed 解决,但它有助于拥有一个始终适用于每个人的无痛工作流程。
  • @JakeMitchell gomvpkg 可以更轻松/更好地进行重命名。 go get golang.org/x/tools/cmd/gomvpkg 然后gomvpkg -help
  • 这个答案让我觉得完全不切实际。从分叉项目中提取项目文件,这太疯狂了?创建拉取请求时你会做什么? Ivan Rave 的回答对我来说似乎是一个更好的解决方案。
  • 这仍然是 Go-lang 的工作方式吗?这太疯狂了,这并不好笑……要么对上游友好,要么对下游友好,但不能两者兼而有之。在我看来,这是一个巨大的设计缺陷,可能是由没有过多跨项目合作的人完成的。 #失败 #GOLANG
【解决方案7】:

您可以克隆到任何您想要的位置,而不是克隆到特定位置。 然后,您可以运行这样的命令,让 Go 引用本地版本:

go mod edit -replace github.com/owner/repo=../repo

https://golang.org/cmd/go#hdr-Module_maintenance

【讨论】:

    【解决方案8】:

    同时使用 vendoring 和子模块

    1. fork github 上的 lib(本例中为 go-mssqldb)
    2. 添加一个 submodule,它将你的 fork 克隆到你的 vendor folder 中,但具有上游 repo 的路径
    3. 更新源代码中的import 语句以指向供应商文件夹(不包括vendor/ 前缀)。例如。 vendor/bob/lib => import "bob/lib"

    例如

    cd ~/go/src/github.com/myproj
    
    mygithubuser=timabell
    upstreamgithubuser=denisenkom
    librepo=go-mssqldb
    
    git submodule add "git@github.com:$mygithubuser/$librepo" "vendor/$upstreamgithubuser/$librepo"
    

    为什么

    这解决了所有我在尝试自己解决这个问题时听说和遇到的问题。

    • lib 中的内部包引用现在可以工作了,因为上游的路径没有改变
    • 重新签出您的项目是可行的,因为子模块系统在正确提交时从您的 fork 中获取它,但在上游文件夹路径中
    • 您不必知道手动破解路径或使用 go 工具。

    更多信息

    【讨论】:

      【解决方案9】:

      现代答案(至少 1.15 或更高版本)。

      go mod init github.com/theirs/repo
      

      创建一个显式的初始化参数,它是原始包名称。如果您不包含 repo 名称,它将假定为 gopath 中的名称。但是当你使用 go 模块时,它们不再关心它们在磁盘上的位置,或者 git 实际从哪里提取依赖项。

      【讨论】:

        【解决方案10】:

        为了自动化这个过程,我编写了一个小脚本。您可以在我的blog 上找到更多详细信息,以便将“gofork”之类的命令添加到您的 bash。

        function gofork() {
          if [ $# -ne 2 ] || [ -z "$1" ] || [ -z "$2" ]; then
            echo 'Usage: gofork yourFork originalModule'
            echo 'Example: gofork github.com/YourName/go-contrib github.com/heirko/go-contrib'
            return
          fi
           echo "Go get fork $1 and replace $2 in GOPATH: $GOPATH"
           go get $1
           go get $2
           currentDir=$PWD
           cd $GOPATH/src/$1
           remote1=$(git config --get remote.origin.url)
           cd $GOPATH/src/$2
           remote2=$(git config --get remote.origin.url)
           cd $currentDir
           rm -rf $GOPATH/src/$2
           mv $GOPATH/src/$1 $GOPATH/src/$2
           cd $GOPATH/src/$2
           git remote add their $remote2
           echo Now in $GOPATH/src/$2 origin remote is $remote1
           echo And in $GOPATH/src/$2 their remote is $remote2
           cd $currentDir
        }
        
        export -f gofork
        

        【讨论】:

        • 第 4 行中的 golang 是否应该更改为 gofork
        • 很好看!修复!
        • 这在几年前有效,但现在不行了。
        【解决方案11】:

        在您的 Gopkg.toml 文件中,在下面添加这些块

        [[constraint]]
          name = "github.com/globalsign/mgo"
          branch = "master"
          source = "github.com/myfork/project2"
        

        因此它将使用分叉的project2 代替github.com/globalsign/mgo

        【讨论】:

        • Gopkg.toml 文件仅供dep 使用,这个问题根本没有提及。新的 Go 项目应该使用 Go 模块(并且 IMO 现有的基于 dep 的项目也应该迁移)。
        • 我不知道这个 dep 功能,你的回答肯定帮了我:)
        【解决方案12】:

        您可以使用命令go get -f 为您获取分叉回购

        【讨论】:

        • 如果您使用的是模块,则不会。