如何在git中执行“仅本地提交”?

时间:2012-11-07 19:37:03

标签: git

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

目前,当我提交将提交重新排序回顶部时,我手动重新绑定,并且我使用HEAD^推送以避免推送本地更改。我也考虑过将这些变化放在藏匿处,但这样做不太方便,因为它排除了正常使用藏匿处。另一种方法是简单地将所有这些本地更改保留为未分级,并在每次要提交时使用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包含对现有版本化文件的更改,并且可能包含任意数量的更改。 (如果我可以进行多次提交"浮动",那会更好,因为我可以将我的本地更改分开)。

9 个答案:

答案 0 :(得分:19)

如何将这些更改放入您从主开发分支定期重新定位/合并的本地分支中?这样就不会有将它们上游的危险。

答案 1 :(得分:14)

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

  • 有些提交应该保密(例如在本地分支上),当你拉动时不要推送或合并;它们应该保留在“共享提交之后”。

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

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

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

这是一个示例脚本,可以同时为您提供:

#!/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 --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从workdir中的已修改文件创建) ,git local commit(名称不完美,令人遗憾)创建一个新的“真实”提交,git local push推送,git local pull拉动等等。

这主要的缺点是它需要你记住大多数命令现在都以local为前缀。如果你忘记这样做一次,你会有点h,但不会太糟糕 - 快速git rebase -i将允许你轻松地将你的本地提交回到顶部然后你再次关闭并再次运行。最大的风险是您不小心使用git push代替git local push并将所有私人更改发送到上游,这会让所有人感到烦恼。为此,您可能希望实际编写一个小包装脚本来调用而不是git本身(称之为~/bin/git并确保~/bin在您的路径上):

#!/bin/bash
if [[ $1 eq 'push' ]]; then
  if git rev-list --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__的任何提交。

答案 2 :(得分:11)

在之前的工作中,每个人都有自己的本地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时,我们会重新定位settings的{​​{1}},然后根据master重新定义我们正在处理的任何主题。

不可否认,这不是一步一步“永远留下这个浮动”的解决方案,但它足够简洁明了。

答案 3 :(得分:9)

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

# git diff > local.patch

如果要提交,请反向修补:

# git apply -R local.patch

提交后,恢复补丁:

# git apply local.patch

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

答案 4 :(得分:2)

假设您在不包含其他需要提交的更改的文件中进行了特定于本地的更改,您只需要让git通过 git update-index 。 特别是,您需要--assume-unchanged或--skip-worktree;虽然它们基本上做同样的事情(在git索引中将文件标记为未更改),但它们具有一些非常微妙的特性,这些特性已被很好地描述here

答案 5 :(得分:1)

如果是实际提交,你试图阻止它向前发展,那么你走在正确的轨道上 - 使用

git rebase -i

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

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

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

答案 6 :(得分:0)

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

以下是对gitignore的一些解释。

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

答案 7 :(得分:0)

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

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

作为一个特例,请考虑.gitignore。这是一个适用于“所有”结账的单个文件,意味着隐含的“独特结账”。为了让.gitignore运行良好,每次结帐需要一个,这是一种区分每个结帐中不同文件的方法。然而git中没有这种基础设施的暗示。

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

如果这会激励任何人攻击git,请添加选项以进行“命名结帐”,并将现有行为保留为匿名结帐。您可以拥有[0..1]匿名签出和[0 .. *)命名签名。从这个基础上,你可能想要的大部分内容都非常自然地遵循。

答案 8 :(得分: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 / ”,提交消息将为“
  • git local-import <name>将通过一次提交(“不快进,不提示,全自动”)将“本地/ <名称>”合并到当前分支中,并重置为初始状态。这样就可以将本地分支修改导入到工作目录中,而无需在当前分支历史上进行任何操作。
  • git local-delete <name>将强制删除分支“ local / ”,以防您要清理本地分支。

我希望这对某人有用。

谢谢。