我可以使用相同的命令将标签/分支作为分支签出吗?

时间:2019-02-22 08:17:14

标签: git

我想编写一个bash函数,将一个标签/分支检出为具有该名称的分支。有办法吗?

我尝试了以下操作,但是它仅适用于标签,不适用于分支。

git checkout -b {1} {2}

这是我看到的:

$ git checkout -b v1.0 v1.0  # works
$ git checkout -b release release  # fails
[ fatal: Cannot update paths and switch to branch 'release' at the same time. ]
$ git checkout -b release origin/release  # works if i prefix origin
$ git checkout release  # works if i don't give anything

我可以将其合并为一个命令吗?

2 个答案:

答案 0 :(得分:2)

$ git checkout -b release release

这意味着:在release指定的提交处创建一个新分支release。因为必须有一个名为release的分支才能起作用,所以无法创建它,并且命令失败。

摘自 git-checkout 文档:

git checkout -b|-B <new_branch> [<start point>]
     

指定 -b 会导致创建新分支,就像调用 git-branch(1)然后将其检出一样。 […]

     

如果给出了 -B ,则创建<new_branch>(如果不存在);否则,将被重置。这是等价交易

$ git branch -f <branch> [<start point>]
$ git checkout <branch>
     

也就是说,除非“ git checkout”成功,否则不会重置/创建分支。

因此,您可以尝试使用-B而不是-b

答案 1 :(得分:1)

使用mkrieger1's answer中描述的方法-B是可行的,但是总的来说,这不是一个好主意。如果我们创建一个分支名称来遮盖标签名称,则会导致问题。

在这种情况下,首先检查各种错误情况可能是最明智的选择。如果存在这些错误情况,请停止并寻求他人的帮助。仅在一切正常的情况下进行。在这里,当分支名称已经作为分支名称存在并且您可以切换到它时,或者当没有分支名称但是有一个已知的起点使用时,就会发生一切正常的情况一些 other 名称,您可以创建分支并切换到该分支。

换句话说,您可能应该允许git checkout existing-branchgit checkout -b new-branch start-point。在这里, existing-branch 应该要求该名称为分支名称。同样, new-branch 变体应要求 start-point 是一个不同于的名称 new-branch ,并且该 new-branch 不是现有的有效标签或其他名称。允许/要求这些需要一些if / else样式逻辑。

背景

假设您有一些短名称字符串 S ,其中 S 可能是releasedevv1.2或其他名称可能是这样(我们可以假设 S 不是原始哈希ID,尽管我们可以实际对其进行测试)。有了这个 S 和一个存储库,至少要针对这个特定问题,要考虑两种情况:

  1. git rev-parse S产生一些哈希ID。 S 可能是现有名称。它可以是分支名称,因此 S refs/heads/S的缩写,也可以是标签名,因此 S refs/tags/S的缩写:

    $ git rev-parse stash-exp
    8dbdf339cd2e757143d9f222f662edd8ef745ea8
    

    因此,stash-exp可能是分支或标记。

  2. 或者,git rev-parse S 不会产生一些哈希ID,但是会失败:

    $ git rev-parse gronk
    gronk
    fatal: ambiguous argument 'gronk': unknown revision or path not in the working tree.
    Use '--' to separate paths from revisions, like this:
    'git <command> [<revision>...] -- [<file>...]'
    

git rev-parse的这种特殊用法可以将名称本身打印到stdout,并将上面的fatal消息打印到stderr(您可以通过将stdout和stderr重定向到两个不同的文件来进行自我测试)。添加--verify会将其更改为仅抱怨(并退出非零=失败):

$ git rev-parse --verify gronk && echo ok || echo failed
fatal: Needed a single revision
failed

git rev-parse --verify stash-exp继续工作时(退出0 =成功):

$ git rev-parse --verify stash-exp && echo ok || echo failed
8dbdf339cd2e757143d9f222f662edd8ef745ea8
ok

请注意,我们可以改为git rev-parse告诉我们引用的全名,否则将失败:

$ git rev-parse --verify --symbolic-full-name stash-exp
refs/heads/stash-exp
$ git rev-parse --verify --symbolic-full-name gronk
fatal: Needed a single revision

由此可见,stash-exp实际上是一个分支名称。同时v2.1.0标签名

$ git rev-parse --verify --symbolic-full-name v2.1.0
refs/tags/v2.1.0

在这里值得注意的是,在更多情况下git rev-parse可以将名称转换为哈希ID,并且并非所有这些都是符号引用。 The gitrevisions documentation包含名称可用的完整列表,包括诸如相对操作之类的内容:HEAD~3master^2~2等。使用--symbolic-full-name可以让git rev-parse告诉我们完整名称,然后可以将其与我们关心的一个或多个模式匹配:

# check whether $name is a branch name
hash=$(git rev-parse --verify "$name" 2>/dev/null) || {
    echo "I do not recognize $name at all" 1>&2
    exit 1
}
fullname=$(git rev-parse --verify --symbolic-full-name "$name" 2>/dev/null) || {
    echo "I can translate $name to $hash but it is not any branch or tag name" 1>&2
    exit 1
}
case "$fullname" in
refs/heads/*) ;; # ok - it is a branch name
*)  echo "$name is really $fullname and that is not a branch" 1>&2
    exit 1;;
esac

因此,以上代码片段验证了$name是否已设置为现有分支名称。它计算当前保存在$hash中的技巧提交哈希ID及其全名,由于rev-parse规则,其全名将为refs/heads/$name,并保存在$fullname

我们为什么在乎?

好吧,要注意的一个原因是git checkout在分支名称和标记名称之间的行为有所不同:

$ git checkout stash-exp
Switched to branch 'stash-exp'

由于stash-exp是分支,因此我们在其上。

$ git checkout v2.1.0
Note: checking out 'v2.1.0'.

You are in 'detached HEAD' state. ... [massive snip]

由于v2.1.0不是分支,因此我们现在位于 no 分支。取而代之的是,Git切换到了这种分离式HEAD模式。

$ git checkout master
Previous HEAD position was 6c4ab27f23 Git 2.1
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

现在我们回到master,它是 的一个分支。因此,是否给git checkout指定分支名称很重要,这可能很重要。

为什么我们还要关心

让我们现在使用git checkout -b创建一个分支名称,该名称是 same 短名称​​ S 作为我们的标签名称v2.1.0

$ git checkout -b v2.1.0 v2.1.0
Switched to a new branch 'v2.1.0'
$ git rev-parse v2.1.0
warning: refname 'v2.1.0' is ambiguous.
7452b4b5786778d5d87f5c90a94fab8936502e20

嗯,这个含糊的东西是新的,不是吗?让我们创建一个新的虚拟提交:

$ git commit --allow-empty -m dummy
[v2.1.0 83429187cf] dummy
$ git show | sed 's/@/ /'
commit 83429187cfe0ff9055453b8c2284deabb21139aa (HEAD -> v2.1.0)
Author: Chris Torek <chris.torek@gmail.com>
Date:   Sat Feb 23 11:44:29 2019 -0800

    dummy
$ 

所以我们分支的最重要的提交就是这个新的提交83429187...

但是:

$ git show v2.1.0 | sed -e 's/@/ /' -e 10q
warning: refname 'v2.1.0' is ambiguous.
tag v2.1.0
Tagger: Junio C Hamano <gitster pobox.com>
Date:   Fri Aug 15 15:09:28 2014 -0700

Git 2.1
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)

iQIcBAABAgAGBQJT7oUZAAoJELC16IaWr+bLD3UP/iqk7c+1BdEjIUks3JS8eUu7
V/sU1dS2K/8ZeeQa9aeqmAxt/9aqeF6DNtN9AcAO5bf2WeGYfKkTdxsb4eWAaw+W
$ 

名称v2.1.0有时表示标签,有时又表示分支。这就是这个“模棱两可”的警告。如果您回到我先前链接的the gitrevisions documentation,您会看到解析符号名的过程分为六步S 为哈希ID。使用 tag 名称的步骤位于使用 branch 名称的步骤之前。

这实际上意味着,很大一部分Git都倾向于使用标签而不是分支。例外情况是知道或假设其参数为< em>分支名称,例如git checkoutgit branch。但这反过来又意味着,如果您创建了含糊不清的名称情况,就会惹上麻烦。

让我们切换回master并删除这个不太好的分支名称:

$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ git branch -D v2.1.0
Deleted branch v2.1.0 (was 83429187cf).

这就是为什么git checkout -B release release不是一个好计划的原因

要使git checkout -B release release工作,release必须是提交的有效符号名。也就是说,git rev-parse release必须有效(实际上,git rev-parse release^{commit}必须有效,尽管我们不需要在这里详细介绍该细节)。

如果 S (代表releasestash-exp或其他名称)是有效的符号名称,则一种可能是 S 是现有分支名称。但是,在这种情况下,git checkout -B S S的操作是将 S 设置为 S 中已经存在的值。这是无害的,但也没有意义: S 已经是一个分支名称,我们可以只运行git checkout S

如果这是一个有效的符号名称,因此它可以成为一个分支名称,但是现在不是一个分支名称,则可能是一个标签名称。在这种情况下,git checkout -B S S将创建 S 作为新的分支名称。我们也可以在这里使用git checkout -b S S,因为 S 还不是 分支名称,正如上面v2.1.0所见,小写字母-b选项有效。但这会导致警告:refname'v2.1.0'是不明确的情况:现在我们既有分支又有标签。如果我们认为这是一个坏主意-并且至少 I 如此认为-那么我们根本就不应该这样做。

这里的结论是,我们应该使用带有{em>现有分支名称的git checkout,将切换为,否则我们应该使用{{ 1}},具有 new 分支名称,该分支名称尚未以其他任何形式使用,因此git checkout -b new-branch start-point表示我不知道该名称 git rev-parse --verify 告诉Git使用什么提交来启动新分支,由于必须将其解析为实际的提交哈希,因此它不能与新的分支名称相同:我们肯定知道新名称根本不会解析为任何东西。