50

我正在使用 git,我希望能够创建一个不与远程存储库同步的提交。这样的提交必须“浮动”在本地存储库中的所有其他提交之上,以避免影响历史记录。我可以使用这样的提交来存储特定于本地的更改(配置更改、调试标志、本地解决方法等)。

目前,我在提交时手动 rebase 以将提交重新排序到顶部,并且我推送 usingHEAD^以避免推送本地更改。我也考虑过将更改放在存储中,但这不太方便,因为它排除了对存储的正常使用。另一种选择是简单地保留所有这些本地更改,并git add -p在每次我想提交时使用。但是,随着大量琐碎的本地更改,这变得很麻烦。

这是我当前工作流程的示例:

我的存储库最初看起来像

A---B---C---F master

其中“F”是我的浮动提交。

我做出承诺:

A---B---C---F---D master

然后git rebase -i HEAD~2重新排序:

A---B---C---D---F master

然后git push remote HEAD~1...推送除本地F提交之外的所有内容。

更改 F 包含对现有版本化文件的更改,并且可能包含任意数量的更改。(如果我可以使多个提交“浮动”,那就更好了,因为我可以分离我的本地更改)。

4

9 回答 9

22

将这些更改放入您从主开发分支定期变基/合并的本地分支怎么样?这样就不会有任何将它们提交到上游的危险。

于 2012-11-07T19:47:45.473 回答
18

所以,听起来你想要两件事:

  • 一些提交应该保持私有(例如在本地分支上),并且在拉取时永远不要推送或合并;他们应该在共享提交“之后”保持不变。

  • 这对您来说应该是最透明的;您想在 master 上工作,并自动维护本地分支。您只需决定哪些提交应该是本地的,与远程存储库交互的命令将忽略这些。

所以你想写一个脚本(命名git-something并把它放在你的路径上,所以这是一个额外的 git 命令)来识别和处理这些提交。您需要一些触发器来让脚本识别本地提交。做到这一点的简单方法是在提交描述中放一个神奇的词——一个你永远不会在真实/共享提交中使用的词——以便脚本识别。(如果这对你来说听起来太不稳定,你也可以在提交的树中使用一个特殊的文件,比如.THIS_COMMIT_IS_LOCAL_ONLY;我在示例中没有这样做,因为它有点难。)

您需要一个命令来从当前索引/工作目录进行本地提交;这很容易,它只是调用git commit $@ -m "__LOCAL_COMMIT_ONLY__"(这是一个例子;关键是它会做一些事情来将正在创建的提交标记为仅限本地,然后推迟到 git 提交)。您还需要一个命令来临时弹出所有本地提交,执行一些其他 git 命令(拉、推、取、合并等),然后重新应用本地提交。您还将使用此命令来创建您打算共享的本地提交,以便它们始终出现在您的历史记录中仅本地提交的“下方”。

这是一个示例脚本,可将两者合二为一:

#!/bin/sh
if [[ $1 eq 'new' ]]; then
  shift
  exec git commit $@ -m "__LOCAL_COMMIT_ONLY__"
elif [[ $1 eq

OLD_HEAD=$(git rev-parse HEAD)
OLD_REAL_HEAD="$(git rev-list HEAD --grep=__LOCAL_COMMIT_ONLY__ | tail -n1)^"
git reset --soft $OLD_REAL_HEAD
git $@
git rebase --onto HEAD $OLD_REAL_HEAD $OLD_HEAD

现在,假设您调用脚本git-local,使用git local new从索引创建一个新的仅本地提交(或git local new -a从工作目录中的修改文件创建),git local commit(遗憾的是,名称不完美)创建一个新的“真实”提交,git local push推、git local pull拉等

这样做的主要缺点是它要求您记住大多数命令现在都以local. 如果您忘记执行此操作,您会感到有些不知所措,但也不算太糟——快速git rebase -i操作可以让您轻松地将本地提交移回顶部,然后您就可以重新开始运行了。最大的风险是您不小心使用git push而不是git local push将所有私人更改发送到上游,这会惹恼所有人。为此,您可能想要实际编写一个小包装脚本来调用而不是 git 本身(调用它~/bin/git并确保~/bin在您的路径上):

#!/bin/sh
if [[ $1 = 'push' ]]; then
  if /usr/bin/git rev-list HEAD --grep=__LOCAL_COMMIT_ONLY__ | grep -q .; then
    echo "Can't push with local changes still active!"
    echo "Try using `git local push' instead."
    exit 1
  fi
fi
exec /usr/bin/git "$@"

您还可以pre-receive在服务器上创建一个挂钩,自动拒绝__LOCAL_COMMIT_ONLY__其消息中包含的任何提交。

于 2012-11-18T18:34:08.493 回答
13

您可以使用可以从 Git 生成的补丁文件。

# git diff > local.patch

当你想提交时,反向补丁:

# git apply -R local.patch

提交后,恢复补丁:

# git apply local.patch

在创建补丁文件之前,您只需确保只有您的本地更改在工作树上。创建补丁文件后,您可以将其添加到其中,.gitignore这样您就不会提交它。

于 2012-11-13T19:21:18.613 回答
12

在以前的工作中,每个人都有自己的本地settings分支,我们在其中提交了我们的个人设置。这个分支是基于master; 任何主题分支都从settings. 当主题准备好进行集成时,我们将它们重新基于master. 这似乎是@koljaTM 的建议。

A---B---C  master
         \
          F  settings
           \
            D  topic

rebase --onto master settings topic

A---B---C  master
        |\
        | F  settings
         \
          D  topic

当新的变化发生时master,我们会变基settingsmaster然后变基我们正在研究的任何主题settings

诚然,这不是一步“让这个永远漂浮”的解决方案,但它足够干净和简单。

于 2012-11-07T20:01:58.967 回答
2

假设您在一个文件中有特定于本地的更改不包含您需要提交的其他更改,您可以通过git update-index要求 git 忽略该文件中的更改。特别是,您需要 --assume-unchanged 或 --skip-worktree;虽然它们基本上做同样的事情(在 git index 中将文件标记为未更改),但它们有一些非常微妙的特性,这些特性在这里得到了很好的描述。

于 2013-04-10T12:04:47.903 回答
1

如果这是您试图阻止继续进行的实际提交,那么您就在正确的轨道上 - 使用

git rebase -i

并将所有“个人提交”重新排序为最新提交,然后

git push <remotename> <latest commit SHA you want to push>:<remotebranchname>

这会将所有提交推送到并包括您提供的 SHA,但不会推送它之后的“个人提交”。

于 2012-11-17T22:21:21.280 回答
0

您可以忽略 git 中的某些文件,这样它们就不会被推送到远程存储库。我这样做的方式是将这些文件保存在本地,并且不要通过忽略它们将它们推送到存储库。

下面是对 gitignore 的一些解释。

https://help.github.com/articles/ignoring-files

于 2012-11-07T19:39:27.393 回答
0

我经常遇到这个问题,通常是从共享代码库中拆分 IDE 设置等。这是一个足够常见的用例,您可能认为它现在已经解决了,但还没有。我不相信git可以随心所欲地处理这个问题。

归根结底,问题是您希望两个结帐共享一个目录。为了使其工作,需要有一些数据结构,可由用户操作,标记哪些文件属于哪些结帐。这也不是一对一的关系;在多个结帐中包含某些文件部分是完全合理的。在所有情况下,您都需要以某种方式命名不同的结帐以支持消歧。为了基本的理智,您需要一个目录列表器,用它们的签出关联注释文件。

作为一个特定的例子,考虑.gitignore. 这是适用于“所有”结帐的单个文件,隐含地表示“唯一结帐”。为了.gitignore正常工作,每次结帐都需要一个,这是一种区分每次结帐中不同文件的方法。然而,在git.

我解决此类问题的“标准”方法是安排事情,以便每个目录结帐一次。我创建了一个名为“custom”(或类似)的目录。该目录进入.gitignore父目录。在自定义目录中,我从不同的存储库和/或不同的位置签出。这意味着两次提交以捕获所有更改;这在实践中很少出现问题。

如果这激发了任何人的攻击git,请添加进行“命名结帐”的选项并将现有行为保留为匿名结帐。您可以有 [0..1] 个匿名结帐和 [0..*) 个命名结帐。在此基础上,您可能想要的大多数其他内容都非常自然地遵循。

于 2012-11-13T19:05:05.047 回答
0

我知道这个问题已经得到解答,但我遇到了类似的问题并最终配置我~/.gitconfig添加三个有用的命令。如果其他解决方案不好,它可能会对某些人有所帮助。

[alias]
    local-export = "![ ${#} -eq 1 ] && CURRENT_BRANCH=$(git branch --show-current) && git checkout -b \"local/${1}\" && git commit --allow-empty -m ${1} && git checkout \"${CURRENT_BRANCH}\" || echo 'You must provide <local_branch_name>' #"
    local-import = "![ ${#} -eq 1 ] && CURRENT_BRANCH=$(git branch --show-current) && git merge \"local/${1}\" --no-ff --commit --no-edit && git reset HEAD^ || echo 'You must provide <local_branch_name>' #"
    local-delete = "![ ${#} -eq 1 ] && git branch -D \"local/${1}\" || echo 'You must provide <local_branch_name>' #"

这些命令允许:

  • git local-export <name>将创建一个带有单个提交的分支,其中包含当前暂存的内容。分支将命名为“local/<name>”,提交消息将命名为“<name>”
  • git local-import <name>将通过一次提交(无快进、无提示、全部自动)将“local/<name>”合并到当前分支并重置为初始状态。这允许将本地分支修改导入工作目录,而无需对当前分支历史执行任何操作。
  • git local-delete <name>如果您想清理本地分支,将强制删除分支“local/<name>”。

我希望这对某人有用。

谢谢你。

于 2020-11-05T11:51:39.283 回答