Git 别名的工作方式与 shell 中的常规别名类似,但它们特定于 Git 命令。它们允许您为较长的命令创建快捷方式或创建默认情况下不可用的新命令。
别名与其他 git 命令在相同的 shell 环境中运行,主要用于简化常见工作流程。
简单别名使用一组参数调用单个 Git 命令。例如,您可以创建一个别名来显示存储库的状态,方法是使用 s 别名运行 git status:
[alias] s = status
然后您可以运行 git 来显示存储库的状态。因为我们在 ~/.gitconfig 中配置了别名,所以它可用于系统上的所有存储库。
您还可以创建运行任意 shell 命令的 git 别名。为此,别名需要以 ! 开头。这告诉 git 执行别名,就好像它不是 git 子命令一样。例如,如果您想依次运行两个 git 命令,您可以创建一个运行 shell 命令的别名:
[alias] my-alias = !git fetch && git rebase origin/master
当您运行 git my-alias 时,此别名按顺序运行 git fetch 和 git rebase origin/main。
git 别名的一个限制是它们不能设置为多行值。这意味着对于更复杂的别名,您需要缩小它们。
此外,在 INI 文件中,;字符用于注释掉该行的其余部分。这意味着您不能使用 ;在您的别名命令中。
这两个限制可能会使使用标准 git 别名语法创建更复杂的别名变得困难,但仍然可以完成。例如,使用 if 进行分支的别名可能如下所示:
[alias] branch-if = !bash -c "'!f() { if [ -z \"$1\" ]; then echo \"Usage: git branch-if\"; else git checkout -b $1; fi; }; f'"
这些限制使得创建和维护其中包含任何形式的控制流的别名变得更加复杂。这就是脚本的用武之地。
您可以使用您喜欢的任何编程语言编写 gitalias 脚本。如果您熟悉 bash 脚本并希望使用它,则可以创建一个运行所需 git 命令的 bash 脚本。事实上,我对 JavaScript 的掌握要强得多,所以我会使用 JavaScript。
另一个主要好处是,通过使用脚本语言,您的别名可以更轻松地获取和操作参数。 Git 会将您在 CLI 上传递的任何参数转发到您的别名,并将其附加到命令末尾。因此,您的脚本应该能够毫无问题地读取它们。例如,在 Node JS 中,您可以直接在 process.argv. 上访问传递给脚本的参数。
设置此功能的基本步骤不会根据所选语言而改变。您需要:
我们需要更新别名来检查分支是 main 还是 master,然后重新设置正确的分支。这是脚本的完美用例。
#!/usr/bin/env node const { execSync } = require('child_process'); // We want to run some commands and not immediately fail if they fail function tryExec(command) { try { return { status: 0 stdout: execSync(command); } } catch (error) { return { status: error.status, stdout: error.stdout, stderr: error.stderr, } } } function getOriginRemoteName() { const { stdout, code } = tryExec("git remote", true); if (code !== 0) { throw new Error("Failed to get remote name. \n" stdout); } // If there is an upstream remote, use that, otherwise use origin return stdout.includes("upstream") ? "upstream" : "origin"; } // --verify returns code 0 if the branch exists, 1 if it does not const hasMain = tryExec('git show-ref --verify refs/heads/main').status === 0; // If main is present, we want to rebase main, otherwise rebase master const branch = hasMain ? 'main' : 'master'; const remote = getOriginRemoteName() // Updates the local branch with the latest changes from the remote execSync(`git fetch ${remote} ${branch}`, {stdio: 'inherit'}); // Rebases the current branch on top of the remote branch execSync(`git rebase ${remote}/${branch}`, {stdio: 'inherit'});目前,要运行脚本,我们需要运行node ~/gitaliases/git-rebase-main.js。这并不理想,也不是你会养成的习惯。我们可以通过创建运行脚本的 git 别名来使这变得更容易。
#!/usr/bin/env node const { execSync } = require('child_process'); // We want to run some commands and not immediately fail if they fail function tryExec(command) { try { return { status: 0 stdout: execSync(command); } } catch (error) { return { status: error.status, stdout: error.stdout, stderr: error.stderr, } } } function getOriginRemoteName() { const { stdout, code } = tryExec("git remote", true); if (code !== 0) { throw new Error("Failed to get remote name. \n" stdout); } // If there is an upstream remote, use that, otherwise use origin return stdout.includes("upstream") ? "upstream" : "origin"; } // --verify returns code 0 if the branch exists, 1 if it does not const hasMain = tryExec('git show-ref --verify refs/heads/main').status === 0; // If main is present, we want to rebase main, otherwise rebase master const branch = hasMain ? 'main' : 'master'; const remote = getOriginRemoteName() // Updates the local branch with the latest changes from the remote execSync(`git fetch ${remote} ${branch}`, {stdio: 'inherit'}); // Rebases the current branch on top of the remote branch execSync(`git rebase ${remote}/${branch}`, {stdio: 'inherit'});现在您可以运行 git rebase-main 来重新设置正确的分支,无论它是 main 还是 master。
案例研究:修改
#!/usr/bin/env node const { execSync } = require('child_process'); // We want to run some commands and not immediately fail if they fail function tryExec(command) { try { return { status: 0 stdout: execSync(command); } } catch (error) { return { status: error.status, stdout: error.stdout, stderr: error.stderr, } } } function getOriginRemoteName() { const { stdout, code } = tryExec("git remote", true); if (code !== 0) { throw new Error("Failed to get remote name. \n" stdout); } // If there is an upstream remote, use that, otherwise use origin return stdout.includes("upstream") ? "upstream" : "origin"; } // --verify returns code 0 if the branch exists, 1 if it does not const hasMain = tryExec('git show-ref --verify refs/heads/main').status === 0; // If main is present, we want to rebase main, otherwise rebase master const branch = hasMain ? 'main' : 'master'; const remote = getOriginRemoteName() // Updates the local branch with the latest changes from the remote execSync(`git fetch ${remote} ${branch}`, {stdio: 'inherit'}); // Rebases the current branch on top of the remote branch execSync(`git rebase ${remote}/${branch}`, {stdio: 'inherit'});这个脚本比上一个脚本稍微复杂一些,因为它有一些控制流。它将检查当前提交是否被其他分支依赖,如果是,则会错误退出。这是为了防止您修改其他分支所依赖的提交,因为这样做会在尝试合并依赖于该提交的分支时导致问题。
设置别名可以使用之前相同的方法:
#!/usr/bin/env node const { execSync } = require('child_process'); // We want to run some commands and not immediately fail if they fail function tryExec(command) { try { return { status: 0 stdout: execSync(command); } } catch (error) { return { status: error.status, stdout: error.stdout, stderr: error.stderr, } } } function getOriginRemoteName() { const { stdout, code } = tryExec("git remote", true); if (code !== 0) { throw new Error("Failed to get remote name. \n" stdout); } // If there is an upstream remote, use that, otherwise use origin return stdout.includes("upstream") ? "upstream" : "origin"; } // --verify returns code 0 if the branch exists, 1 if it does not const hasMain = tryExec('git show-ref --verify refs/heads/main').status === 0; // If main is present, we want to rebase main, otherwise rebase master const branch = hasMain ? 'main' : 'master'; const remote = getOriginRemoteName() // Updates the local branch with the latest changes from the remote execSync(`git fetch ${remote} ${branch}`, {stdio: 'inherit'}); // Rebases the current branch on top of the remote branch execSync(`git rebase ${remote}/${branch}`, {stdio: 'inherit'});现在您可以运行 git amend 来修改最后一次提交,或者运行 git amend undo 来撤消最后一次修改。这是我最初在 gitconfig 中内联编写的脚本,但随着它变得越来越复杂,我将其移至脚本文件中。这是管理别名复杂性的好方法。为了比较,这里是原始别名:
#!/usr/bin/env node const { execSync } = require('child_process'); // We want to run some commands and not immediately fail if they fail function tryExec(command) { try { return { status: 0 stdout: execSync(command); } } catch (error) { return { status: error.status, stdout: error.stdout, stderr: error.stderr, } } } function getOriginRemoteName() { const { stdout, code } = tryExec("git remote", true); if (code !== 0) { throw new Error("Failed to get remote name. \n" stdout); } // If there is an upstream remote, use that, otherwise use origin return stdout.includes("upstream") ? "upstream" : "origin"; } // --verify returns code 0 if the branch exists, 1 if it does not const hasMain = tryExec('git show-ref --verify refs/heads/main').status === 0; // If main is present, we want to rebase main, otherwise rebase master const branch = hasMain ? 'main' : 'master'; const remote = getOriginRemoteName() // Updates the local branch with the latest changes from the remote execSync(`git fetch ${remote} ${branch}`, {stdio: 'inherit'}); // Rebases the current branch on top of the remote branch execSync(`git rebase ${remote}/${branch}`, {stdio: 'inherit'});这个脚本也可以提取到 .sh 文件中,但是将内容保留在节点中可以减轻我个人的维护负担。过去,每当我需要更新此别名时,我都必须将其粘贴到 bash linter 中,进行更改,缩小它,然后将其粘贴回我的 gitconfig 中。这很痛苦,因此我经常避免更新别名。现在它位于脚本文件中,我可以像任何其他脚本一样更新它。
一些注意事项
像这样设置别名时,请务必记住脚本的 cwd 将是运行脚本的 shell 的当前工作目录。脚本中的任何相对文件路径都将被视为相对于 shell 的 cwd,而不是脚本的位置。这有时非常有用,但有时却非常痛苦。对于我们的 rebase-main 脚本来说,这不是问题,发生这种情况的唯一迹象是我们在文件路径中使用 ~ 将脚本位置引用为绝对路径。
将脚本引入您的 git 别名也可以让您向别名添加越来越多的逻辑。这会使它们更难维护和理解,但也更难记住。不值得维护一个超级复杂的别名,因为无论如何你都不太可能使用它。此外,您应该小心,不要引入任何可能需要很长时间才能遇到别名的内容。如果您正在运行一个需要很长时间才能运行的脚本,您可能需要考虑它是否适合它。
结论
免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。
Copyright© 2022 湘ICP备2022001581号-3