如何别名git-pipe线指令? [英] How to aliasing git-pipe line instruction?

查看:121
本文介绍了如何别名git-pipe线指令?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我输入

  Chois @ Chois-MacPro book-example $ git config --global alias.next'checkout`git log --reverse --ancestry-path HEAD..master |头-n 1 | cut -d \ -f 2`'
Chois @ Chois-MacPro book-example $ git next
error:未知选项`reverse'
usage:git checkout [< options>] <分支>
或:git checkout [< options>] [< branch>] - < file> ...

--q, - 禁止进度报告
-b<分支>创建并签出新分支
-B<分支>创建/重置并签出分支
-l为新分支创建引用日志
--detach在命名提交时分离HEAD
-t,--track为新分支设置上游信息
--orphan< new-branch>
新的未登记分支
-2, - 结帐我们的未合并文件版本
-3, - 他们为未合并文件签出他们的版本
-f,--force force checkout(扔掉本地修改)
-m,--merge执行与新分支的3路合并
--overwrite-ignore更新忽略文件(默认)
- 冲突< ;风格>冲突风格(合并或diff3)
-p, - 交互式地选择区块
--ignore-skip-worktree-bits
不限制pathspecs仅用于稀疏输入
- -ignore-other-worktrees
不检查另一个工作树是否持有给定的ref
- 进度强制进度报告

需要你的帮助。



抱歉附加图片而不是真实命令,但我无法表达符号`本身。

解决方案

如果Git别名前面没有感叹号,Git会尝试运行别名,这要求它不使用任何特殊的shell功能。



您使用的shell功能是反引号扩展 1 和管道( cmd1 | cmd2 )。因此,您的特定别名需要表单。



我看到别名背后的想法;你可以简化这个别名。但是你也可能希望使它更健壮一点,这将需要一个小的shell脚本(它可以嵌入到别名中,尽管这使得它更难以阅读)。



什么下一步确实如此,它的小小缺陷(如果您已经知道,请跳至最后)



rev-spec HEAD..master 表示从 master 无法从头部到达的提交。例如,给定一个相当复杂的图:

  o  -  o  -  o  -  o<  -  brA 
/ / \
...-- o - o - o - o - o - o< - master
\
o-- o - o < - brB
\
o - o - o < - brC

序列 brA..master 让你获得这个子集(提交 * -ed,未提交的提交 x -ed,无聊的不相关的提交留下 o ):

  x  -  x  -  x  -  x < -  brA 
/ / \
...-- x - x - x - * - * - * < - master
\
o - o - o - - brB
\
o - o - o< - brC

和<$ c $

  *  -  *  -  *  - *<  -  brA 
/ / \
... - x - * - * - * --- * - * < - master
\\ \\
x - x - x < - brB
\
o - o - o < - brC

使用 - 祖先路径将选定的提交列表修剪为那些后代的用左手名称标识的提交(它们必须已经是右手名称的祖先,所以这个约束没有效果)。对于第一种情况, brA..master ,这将删除一个 * -ed commit,我将用 here:

  x  -  x  -  x  -  x < ;  -  bA 
/ / \\
... - x - x - x - ! - * - * < - master
\
o - o - o - - brB
\
o - o - o - - brC
pre>

,并且确实将您引导至 master 方向的下一个提交。 p>

然而,对于 brB..master 的情况, - 祖先路径 * 提交中的 none 是c的后代,所以 c>删除所有提交并且该集合变为空。 brB 的提示:

 ! - ! - ! -  - ! <  -  brA 
/ / \
...-- x - ! - ! - !---! - ! < - 主
\\ $
x - x - x< - brB
\
o - o - o< - brC

当然,我们只是使用 HEAD ,这样所有这些都可以在分离的HEAD情况下​​使用。但是也要考虑这个图,这是一种承诺的苯环。我会用 H 标记当前 HEAD 提交:

  o  -  o 
/ \
...-- H o - o < - master
\ /
o - o

在这种情况下, HEAD 是苯环的左边缘。然后 HEAD..master 选择所有戒指以及最右边的 o

  *  -  * 
/ \
...-- H * - * < - master
\ /
* - *

同时, - -ancestry-path 完全不会删除任何内容:每个 * -ed commit实际上是 H 下一个别名会从半随机中选择其中一个 - 让我们假设它选择最上面的一个 - 然后移动到它:

  H  -  o 
/ \
... - o o - o < - master
\ /
o - o

此时下一个 code>永远不能遍历环的下半部分,因为这两个提交不再是 HEAD 的后代。



换句话说,这个小缺陷是假定 HEAD 和提示之间没有内部的分支和合并序列 master 。如果有这样的序列,并且我们希望访问它们,我们必须记录原始的 - 祖先路径一组提交ID,然后再穿过它们(以我们喜欢的任何顺序,可能是一种逆向拓扑排序,让我们在穿过上半部分之后跳回到苯环的下半部分,在我们的特定示例中) 。



简化别名



git log ... |头-1 | cut ... does生成提交ID和日志消息列表,提取第一行(读取 commit< id> ,并从中提取ID。



我们可以用 git rev-list 来做同样的事情。我们仍然需要头部-1 (或等价物),因为 - 没有散步 -n 1 给我们提供了 master 本身的提交ID,而不是下一次提交:

  git rev-list --reverse --ancestry-path ..master | head -1 

(我们可以省略单词 HEAD ,因为这是默认值)。

如果这样会产生 no 输出,那么我们可能不应该运行 git checkout ,因此:

  id = $(git rev-list --reverse --ancestry-path ..master | head -1)
test -n$ id && git checkout$ id|| echo'no more commitits'

即一条双线CRIPT。由于需要引用所有引用,将其嵌入到别名中有点棘手。相反,我只是把它做成一个两行的shell脚本;但如果你想它作为别名:

  [别名] 
next =!f(){id = $(git rev-list --reverse --ancestry-path ..master | head -1); \
test -n \$ id\&&& git checkout $ id || echo'no more commitits';}; f

这两个都可以取目标名称,而不是硬编码 master 。 (但是我也只是将所有提交ID转储到一个文件中,然后逐步遍历文件,也许会用next和prev别名。)






1 通常写作 $(command)而不是命令`,因为括号内嵌的表格很容易理解。无论哪种方式,它都需要shell。



2 可以记录开始点和结束点,并使用更复杂的算法根据 HEAD 现在的位置和我们已知的遍历顺序选择下一个节点。例如,假设我们有起点和终点。我们可以将完整列表生成到一个文件中,然后 git rev-parse HEAD 并找到它在文件中的位置,然后进入文件中的下一个列表,并删除文件,我们将不需要,直到我们再次前进。但实际上,只需将列表转储到文件中一次,然后逐个访问每个条目,而不必在每一步重新生成此文件。

When I type in terminal, it works.

But whem I'm trying to aliasing this command , it occurs error:

 Chois@Chois-MacPro book-example $git config --global alias.next 'checkout `git log --reverse --ancestry-path HEAD..master | head -n 1 | cut -d \  -f 2`'
 Chois@Chois-MacPro book-example $git next
error: unknown option `reverse'
usage: git checkout [<options>] <branch>
   or: git checkout [<options>] [<branch>] -- <file>...

    -q, --quiet           suppress progress reporting
    -b <branch>           create and checkout a new branch
    -B <branch>           create/reset and checkout a branch
    -l                    create reflog for new branch
    --detach              detach the HEAD at named commit
    -t, --track           set upstream info for new branch
    --orphan <new-branch>
                          new unparented branch
    -2, --ours            checkout our version for unmerged files
    -3, --theirs          checkout their version for unmerged files
    -f, --force           force checkout (throw away local modifications)
    -m, --merge           perform a 3-way merge with the new branch
    --overwrite-ignore    update ignored files (default)
    --conflict <style>    conflict style (merge or diff3)
    -p, --patch           select hunks interactively
    --ignore-skip-worktree-bits
                          do not limit pathspecs to sparse entries only
    --ignore-other-worktrees
                          do not check if another worktree is holding the given ref
    --progress            force progress reporting

Need your help.

Sorry for attaching image instead of real command but I can not express symbol ` itself.

解决方案

If a Git alias is not prefixed with an exclamation point !, Git tries to run the alias within itself, which requires that it not use any special shell features.

The shell features you are using are backquote expansion1 and piping (cmd1 | cmd2). Thus, your particular alias requires the ! form.

I see the idea behind the alias; you can simplify this alias a bit. But you might also want to make it a bit more robust, which will require a small shell script (it can be embedded into the alias, although that makes it harder to read).

What next does, and its slight defect (skip to the end if you already know)

The rev-spec HEAD..master denotes commits reachable from master that are not reachable from HEAD. For instance, given a rather complex graph:

       o--o--o--o        <-- brA
      /     /    \
...--o--o--o--o---o--o   <-- master
      \
       o--o--o           <-- brB
        \
         o--o--o         <-- brC

the sequence brA..master gets you this subset (taken commits *-ed, not-taken commits x-ed, boring unrelated commits left o):

       x--x--x--x        <-- brA
      /     /    \
...--x--x--x--*---*--*   <-- master
      \
       o--o--o           <-- brB
        \
         o--o--o         <-- brC

and brB..master gets you these:

       *--*--*--*        <-- brA
      /     /    \
...--x--*--*--*---*--*   <-- master
      \
       x--x--x           <-- brB
        \
         o--o--o         <-- brC

Using --ancestry-path trims the list of selected commits to just those that are descendents of the commit identified by the left-hand name (they must already be ancestors of the right hand name so this constraint has no effect). For the first case, brA..master, this drops one *-ed commit, which I will mark with ! here:

       x--x--x--x        <-- brA
      /     /    \
...--x--x--x--!---*--*   <-- master
      \
       o--o--o           <-- brB
        \
         o--o--o         <-- brC

and that, indeed, takes you to the "next" commit in the direction of master.

For the brB..master case, however, --ancestry-path removes all commits and the set becomes empty, because none of the * commits is a descendent of the tip of brB:

       !--!--!--!        <-- brA
      /     /    \
...--x--!--!--!---!--!   <-- master
      \
       x--x--x           <-- brB
        \
         o--o--o         <-- brC

Of course, rather than a branch name, we just use HEAD so that all this works with the detached-HEAD cases. But consider this graph as well, a sort of benzene ring of commits. I'll mark the current HEAD commit with H:

       o--o
      /    \
...--H      o--o   <-- master
      \    /
       o--o

In this case, HEAD is the left edge of the "benzene ring". Then HEAD..master selects all of the ring as well as the rightmost o:

       *--*
      /    \
...--H      *--*   <-- master
      \    /
       *--*

Meanwhile, --ancestry-path removes nothing at all: every *-ed commit is in fact a descendant of H. The next alias will choose one of these at semi-random—let's assume it chooses the top one—and move to it:

       H--o
      /    \
...--o      o--o   <-- master
      \    /
       o--o

and at this point next can never traverse the lower half of the ring, as those two commits are no longer descendants of HEAD.

In other words, the slight defect is that this assumes there are no internal branch-and-merge sequences between HEAD and the tip of master. If there are such sequences, and we wish to visit them, we must2 record the original --ancestry-path set of commit IDs once, up front, and then walk through them all (in whatever order we like, probably a reverse topological sort that allows us to jump back to the "lower half" of the benzene ring after traversing the "upper half", in our particular example).

Simplifying the alias

What git log ... | head -1 | cut ... does is produce the list of commit IDs and log messages, extract just the first line (which reads commit <id>, and extract just the ID from it.

We can do the same thing more simply with git rev-list. We still need the head -1 (or equivalent) because --no-walk or -n 1 gives us the commit ID of master itself, rather than the next commit:

git rev-list --reverse --ancestry-path ..master | head -1

(we get to omit the word HEAD too since that is the default).

If this produces no output, though, we probably should not run git checkout. Hence:

id=$(git rev-list --reverse --ancestry-path ..master | head -1)
test -n "$id" && git checkout "$id" || echo 'no more commits'

which is a two-line script. To embed this into an alias is a bit tricky because of all the quoting needed. Instead, I'd just make it a two-line shell script; but if you want it as an alias:

[alias]
  next = "!f() { id=$(git rev-list --reverse --ancestry-path ..master | head -1); \
  test -n \"$id\" && git checkout $id || echo 'no more commits'; }; f"

Both of these could take the name of the target, rather than hardcoding master. (But again I'd just dump all the commit IDs into a file and step through the file, with "next" and "prev" aliases, perhaps.)


1This is usually better written as $(command) instead of `command` since the parenthesized form nests and is easier to comprehend. Either way, it requires the shell, though.

2It's possible to just record the start and end points, and use a more complex algorithm to pick the next node based on where HEAD is now and our known traversal order. For instance, suppose we have the start and end points. We can generate the full list into a file, then git rev-parse HEAD and find where it is in the file, then step to the next one in the file and remove the file, which we won't need until we go to step forward again. But really, it's far easier to just dump the list into the file once, and then visit each entry one at a time without regenerating this file on each step.

这篇关于如何别名git-pipe线指令?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆