让我用 Git 术语而不是 Ruby 代码重新表达这个任务。
您希望:
从某个 URL 克隆存储库。然后,我们将该 URL 保存在通常的“远程”名称下,origin。
-
给定分支名称,例如 foo,检查该特定分支(以便当前提交是该分支的提示提交)。
如果分支可以从远程跟踪分支派生,例如(例如)对于通常从 origin/master 派生的master 通常是正确的——您希望 Git 在本地创建此分支,并使用相应的远程跟踪分支设置为其上游,准备对其进行处理。因此,如果分支foo 存在于origin 上的Git 存储库中,那么origin/foo 将存在于本地存储库中,您希望创建本地分支foo,并将origin/foo 作为其上游。
但是,如果没有——如果没有相应的上游名称,那么此时,该分支将成为一个新分支——您希望创建该新分支,使其指向与 @987654330 相同的提交@ 将指向。在这种情况下,您还想立即(或尽可能快地)请求origin 上的 Git 也 创建这个分支名称,指向同一个提交,并在成功时,将foo 设置为将origin/foo 作为其上游。理想情况下,这个过程的最终结果是本地分支foo 存在并且有origin/foo 作为其上游。
您已经观察到,如果foo 存在于远程,git clone -b foo <url> <directory> 会在一个干净的步骤中完成任务(尽管作为副作用,本地克隆不会有master分支呢!)。但是,如果foo 确实 不 存在于遥控器上,则克隆失败。
不幸的是,没有一个 Git 命令可以做到这一切。此外,这里还有一个原子性问题(“原子性”在数据库或并行编程术语中具有其通常含义):在克隆步骤期间foo 不存在这一事实并不意味着@987654341 @ 在您要求上游存储库创建它时将不存在。
所有这一切的“最佳”答案取决于您对这个原子性问题的关心程度(解决它通常只是将原子性问题移到后面的推送步骤,因为到那时可以在服务器上删除分支foo,或者已经获得了额外的提交,或者被重绕和重写,或者其他)。但最终你必须使用多个 Git 命令。
方法一
使用最少网络流量的序列是在没有-b的情况下进行克隆。在这种情况下,您的克隆将自行检查某个分支 — 通常是 master,但实际选择的分支将取决于将存储在远程的 URL 中 Git 的 HEAD 条目中的内容.然后,您的克隆将照常保存遥控器的 URL,名称为 origin(或您提供的任何 -o 参数)。
现在您可以简单地尝试git checkout foo。 foo 已经是当前分支(因为它在远程的HEAD 中),所以这是一个成功的空操作;或 foo 不是当前分支。如果foo 不是当前分支,当且仅当origin/foo 存在时,thish 将创建foo 作为本地分支,并将origin/foo 设置为其上游。这个origin/foo 将反过来存在当且仅当一个名为foo 的分支存在于远程在您进行克隆时(参见“原子性”)。
如果git checkout 失败,您可以假设origin/foo 不存在。 (唯一的另一种可能性是事情发生了非常严重的错误,例如,您的磁盘空间不足或存储设备出现故障,或者 Git 中存在错误:在这两种情况下,所有赌注都没有。)你可以在这个指向您的“创建foo 指向与origin/master 相同的提交并使用git push -u 要求在origin 上创建它”路径,并验证这一切是否有效。与git push 一样,您现在正在与创建foo 的任何人else 竞争。另请注意,如果在您进行克隆时另一个 Git 上没有 master,那么您自己的存储库中可能没有origin/master。
方法二
您可以像现在一样使用git ls-remote,它对远程执行一个完整的往返操作(当前通过URL,因为还没有本地克隆,因此没有名为origin 的远程到store 该 URL)以确定它具有的引用集。如果该存储库中不存在foo,您可以要求 Git 创建它。如果您愿意,您可以稍微不同地执行此操作,在一个新的存储库中使用一系列本地 Git 操作,但该存储库中还没有任何内容:
mkdir <directory>
cd <directory>
git init
git remote add origin <url>
此时您可以运行git ls-remote origin,因为现在是一个名为origin 的遥控器。但是,根本没有本地分支机构。现在我们遇到了通常的原子性问题,“下一步做什么”再次取决于您希望如何解决它们。但如果我不使用方法 1 或它的一些轻微变体,这就是我接下来要做的:
# assumes $branch is set to "foo" as needed, and that
# function "die" prints an error message and exits with failure
git fetch origin # bring over all commits and origin/* branches
if branchrev=$(git rev-parse -q --verify origin/$branch); then
# origin/$branch exists, so we want to act like "git clone -b $branch"
git checkout $branch ||
die "unable to check out $branch, cannot proceed"
else
# origin/$branch does not exist: ask to create it pointing to
# origin/master
rev=$(git rev-parse -q --verify origin/master) ||
die "no origin/master exists, cannot proceed"
git checkout -b $branch $rev ||
die "failed to create $branch"
git push -u origin "$branch:refs/heads/$branch" ||
die "failed to create $branch on origin"
fi
git checkout -b 在本地存储库中创建分支,并将其设置为当前分支。由于初始提交 ID 由原始提交哈希给出(由于 $rev 包含来自 git rev-parse 的结果),它将没有上游。您可以改为使用git checkout -b $branch origin/master,但这会将新分支的上游设置为origin/master,如果git push -u 由于某种原因(例如网络故障)失败,则会为粗心的人留下一个陷阱。你可以使用git checkout --no-track -b $branch origin/master,但考虑到测试以确保origin/master 是一个有效的名称,我们不妨将哈希ID 保存在$rev 中并使用它。
同样的shell脚本——如果你愿意,你可以用Ruby重写——可以在一个普通的旧git clone之后使用,而不是使用有点晦涩的git init; git remote add ...; git fetch序列来完成git clone会做的所有事情除了远程HEAD所指示的分支的初始git checkout。
(换句话说,在实践中,我会先运行git clone——没有棘手的-b部分——然后在上面的shell脚本部分中执行所有操作除了@987654391 @ step,通常在 clone step 之后是不必要的。如果克隆需要很长时间,额外的 git fetch 可能仍然有用,因为这会缩小原子性竞赛,代价是多一个往返于origin 的服务器。不过,没有什么可以完全结束比赛。)