如何在git中进行“仅限本地提交”?
我使用的是git,我希望能够创build一个未与远程仓库同步的提交。 这样的提交将不得不在本地存储库中的所有其他提交上“浮动”,以避免影响历史。 我可以使用这种提交来存储本地特定的更改(configuration更改,debugging标志,本地解决方法等)。
目前,当我提交重新sorting提交到顶端时,我手动重新绑定,并且我使用HEAD^
来避免推送本地更改。 我也考虑过把这些变化放在藏匿处,但是这样做不太方便,因为它排除了藏匿处的正常使用。 另一种方法是简单地将所有这些本地更改留空,并且每次我要提交时都使用git add -p
。 然而,随着大量琐碎的地方变化,这成为一个麻烦。
以下是我目前工作stream程的一个例子:
我的仓库最初看起来像
A---B---C---F master
其中“F”是我的浮动提交。
我做了一个承诺:
A---B---C---F---D master
然后git rebase -i HEAD~2
重新sorting:
A---B---C---D---F master
然后git push remote HEAD~1...
推动一切,但本地F
提交。
更改F包含对现有版本化文件的更改,并可能包含任意数量的更改。 (如果我可以使多个提交“浮动”,那会更好,因为我可以分开我的本地更改)。
把这些变化放到一个本地分支,你可以定期从你的主开发分支进行分支/合并 这样就不会有任何上游的危险。
所以,这听起来像你想要两件事情:
-
一些提交应该保持私有(例如在本地分支上),并且在你拉时从不推动或合并。 他们应该保持在共享提交之后。
-
这对你来说应该是最透明的。 你想在master上工作,并自动维护本地分支。 您只需确定哪些提交应该是本地的,而与远程存储库交互的命令将会忽略这些提交。
所以你想写一个脚本(把它命名为git-something
并把它放在你的path上,所以它是一个额外的git命令)来识别和处理这些提交。 您需要一些触发器以便脚本识别本地提交。 简单的方法是在提交描述中添加一个魔术字 – 一个永远不会用在真实/共享提交中的脚本 – 用于脚本识别。 (如果这太.THIS_COMMIT_IS_LOCAL_ONLY
了,你也可以在提交的树中使用一个特殊的文件,比如.THIS_COMMIT_IS_LOCAL_ONLY
;我没有在例子中做这个,因为它.THIS_COMMIT_IS_LOCAL_ONLY
。)
你需要一个命令来从当前的索引/工作目录进行本地提交; 这很容易,它只是调用git commit $@ -m "__LOCAL_COMMIT_ONLY__"
(这是一个例子;重点是它做一些标记提交被创build为本地,然后按照git提交)。 你还需要一个命令临时popup所有的本地提交,做一些其他的git命令(拉,推,取,合并等等),然后重新应用本地提交。 您还将使用此命令创build您打算共享的本地提交,以便它们始终显示在历史logging中仅提交本地的“提交”下方。
下面是一个示例脚本,可以在一个脚本中给你:
#!/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
从索引创build一个仅用于本地的提交(或者git local new -a
从workdir中的修改文件创build), git local commit
(名称是不完美的,可悲的是)创build一个新的“真正的”提交, git local push
推动, git local pull
拉动,等等。
这样做的主要缺点是它要求你记住大多数命令现在都以local
为前缀。 如果你忘了这样做,那么你有一点漏洞,但不是太糟糕 – 一个快速的git rebase -i
将允许你轻松地将你的本地提交移回顶端,然后再次运行。 最大的风险是你不小心使用git push
而不是git local push
并把你所有的私有变更发送到上游,这会让每个人都感到烦恼。 为此,你可能希望实际编写一个小的包装脚本来调用,而不是git本身(称为~/bin/git
并确保~/bin
在你的path上):
#!/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 "$@"
您也可以在服务器上创build一个pre-receive
钩子,自动拒绝其消息中包含__LOCAL_COMMIT_ONLY__
任何提交。
在以前的工作中,每个人都有自己的本地settings
分支,我们在其中进行个人设置。 这个分支是根据master
; 任何主题分支都从settings
分支。 当话题准备好进行整合时,我们将它们重新发布到master
。 这似乎是@koljaTM的build议。
A---B---C master \ F settings \ D topic rebase --onto master settings topic A---B---C master |\ | F settings \ D topic
当新的变化击中了master
,我们会重新settings
master
settings
,然后重新settings
我们正在settings
的任何主题。
无可否认,这不是一步一步的“离开这个永远浮动”的解决scheme,但它足够干净和简单。
你可以使用一个可以从Git生成的补丁文件。
# git diff > local.patch
当你想提交时,反向修补程序:
# git apply -R local.patch
提交后,恢复补丁:
# git apply local.patch
在创build补丁文件之前,您只需确保只有您的本地更改在工作树上。 创build补丁文件后,可以将其添加到.gitignore
以免提交。
假设你在一个文件中没有包含你需要提交的其他修改的特定于本地的修改,你可以通过git update-index来让git忽略该文件中的修改。 特别是,您需要–assume-unchanged或–skip-worktree; 虽然他们基本上做同样的事情(在git索引中将文件标记为不变),但是它们有一些非常微妙的特性,在这里有很好的描述。
如果这是实际的提交,你试图阻止前进,你是在正确的轨道 – 使用
git rebase -i
然后将所有“个人提交”重新sorting为最新的提交
git push <remotename> <latest commit SHA you want to push>:<remotebranchname>
这将推动所有提交,包括你提供的SHA,但不包括后面提到的“个人提交”。
你可以忽略git中的某些文件,这样它们就不会被推送到远程仓库。 我这样做的方式是将这些文件保存在本地,不要通过忽略它们将它们推送到存储库。
这里是对gitignore的一些解释。
我经常遇到这个问题,通常是将IDE设置等从共享代码库中分离出来。 这是一个常见的用例,你可能会认为它现在已经被解决了,但还没有。 我不相信, git
可以处理这个你想要的。
这个问题归根结底就是你想要两个签出共享一个目录。 为了这个工作,需要有一些数据结构,用户可以操作,标记哪些文件属于哪个签出。 这也不是1-1的关系; 有一个以上的结帐文件的一部分是完全合理的。 在所有情况下,您都需要一些方法来命名不同的结帐以支持消除歧义。 为了基本的理智,你需要一个目录列表器,用它们的签出关联标注文件。
作为一个特别的例子,考虑.gitignore
。 这是一个单独的文件,适用于“所有”结帐,意味着隐含的“唯一结帐”。 为了使.gitignore
良好,每次结帐都需要一个,以便区分每个结帐中的不同文件。 然而这种基础设施并没有暗示git
。
我解决这些问题的“标准”方式是安排事物,因此每个目录都有一个结帐。 我创build一个名为“自定义”(或类似的)的目录。 该目录进入到父母的.gitignore
中。 在自定义目录中,我从不同的存储库和/或不同的位置签出。 这意味着两个承诺捕获所有的变化; 这在实践中很less是一个问题。
如果这启发任何人破解git
,添加选项进行“命名结帐”,并保留现有的行为作为匿名结账。 你可以有[0..1]匿名结账和[0 .. *)名字。 从这个基础上,你可能需要的其他东西都可以很自然地遵循