git - 跨所有分支的相同全局远程 gitignore 文件
问题描述
我想要实现的是拥有一个 .gitignore 文件(由 git 跟踪),该文件在远程存储库(托管在 GitHub 上)中的所有分支之间同步,因此也在相应的本地分支上。我目前使用的 .gitignore 文件并不完美,所以偶尔(有时每天多次)我必须更新它。那么问题是我必须checkout
手动跨所有分支使用 .gitignore 文件,随着创建更多分支,这变得越来越痛苦。因此,对于我所做的每个分支(在分支主控中使用更新的 .gitignore)
git checkout some-outdated-branch
git checkout master .gitignore
git add .gitignore
git rm -r --cached .
git add .
git commit -m "Updated .gitignore and fixed tracked files"
由于这对于多个分支来说相对耗时,我试图寻找一种方法让一个 .gitignore 文件在分支主控(或单独的 gitignore-branch 分支)中自动同步所有分支(本地,以及远程推时)。
这里的问题是我不想使用git config --global core.excludesfile /path/to/local/.gitignore
(如此处所建议的),因为我希望我的项目合作伙伴也使用该特定的 .gitignore 文件,而不必为此更改git config
文件。在此评论中,其他人正在问这个问题,但尚未得到回答。我也无法在 Stack Overflow 上找到关于我的问题的任何答案。
简短摘要
我只想在一个分支上编辑 .gitignore 文件,并以省时省力的方式将该更改与所有其他分支(自动)同步。之后,我想将所有分支中的更改推送到远程存储库(最好只使用一行或几行代码,而不必为每个分支重新提交带有相应提交消息的提交)。
解决方案
不幸的是,只要.gitignore
(或实际上任何文件)被跟踪(意味着在 index 中),该文件的逻辑上独立的副本就会进入您所做的每个提交。这样做的结果是不可能实现你想要的。
正如phd 所提到的,最接近的方法是在每个新提交中存储一个.gitignore
符号链接类型的条目(120000
Git-internal-ese 中的模式)。然后,即使每个提交都有链接目标路径名的逻辑分离(可能是物理共享)副本,当 Git 去读取它的内容时,.gitignore
它也会读取目标路径名的内容,而不是.gitignore
工作树文件那只是从您告诉退出的任何提交中复制出来的git checkout
。
但是,您可以自动化.gitignore
跨多个提交更新文件的过程。最简单的方法可能是使用git worktree add
创建一个单独的工作树来进行更新。这假设您的 Git 版本至少为 2.5,最好至少为 2.15(以避免git worktree
.
以下是一个完全未经测试的脚本,对于每个远程跟踪分支,将使用添加的工作树确保该远程跟踪分支的提示提交包含.gitignore
与主存储库中当前分支中的那个匹配的. 它使用分离的 HEAD 模式来实现这一点(并且在适当的时候一次推送多个提交)。它不能正确处理具有单个 URL 的多个远程名称;为此,请删除git fetch --all
并取消注释new_remote
.
#! /bin/sh
#
# git-update-ignores-across-remote-tracking-branches
. git-sh-setup # get script goodies, and make sure we're at top level
require_work_tree # make sure we have a work-tree, too
# Where is our ignore file? (absolute path)
IFILE=$(readlink -f .gitignore) || die "cannot find .gitignore file"
# set up a temporary file; remove it on exit
TF=$(mktemp) || die "cannot create temporary file"
trap "rm -f $TF" 0 1 2 3 15
# Use a work-tree in ../update-ignores
if [ ! -d ../update-ignores ]; then
[ -e ../update-ignores ] &&
die "../update-ignores exists but is not a directory"
git worktree add ../update-ignores --detach ||
die "unable to create ../update-ignores"
else
# Should use git worktree list --porcelain to verify that
# ../update-ignores is an added, detached work-tree, but
# I leave that to someone else. It might also be good to
# leave remote-tracking names for other added work-trees
# alone, but again, that's for someone else to write.
fi
# Find upstream of current branch, if we're on a branch and there is
# an upstream - we won't attempt to do anything to that one, so as to
# avoid creating headaches for the main work-tree. Note that this
# sets UPSTREAM="" if the rev-parse fails.
UPSTREAM=$(git rev-parse --symbolic-full-name HEAD@{u} 2>/dev/null)
# Now attempt to update remote-tracking names. Update all remotes
# first so that we are in sync, then list all names into temporary file.
# From here on, we'll work in the update-ignores work-tree.
cd ../update-ignores
require_clean_work_tree "update ignores"
git fetch --all || die "unable to fetch --all"
git for-each-ref --format='%(refname)' refs/remotes > $TF
REMOTE=
UPDATED=
# Function: push UPDATED to REMOTE. Set REMOTE to $1 and clear UPDATED.
# Does nothing if UPDATED or REMOTE are empty, so safe to use an extra time.
new_remote() {
local u="$UPDATED" r="$REMOTE"
if [ "$u" != "" -a "$r" != "" ]; then
git push $r $u || die "failed to push!"
fi
UPDATED=
REMOTE=$1
# [ -z "$REMOTE" ] || git fetch $REMOTE || die "unable to fetch from $REMOTE"
}
while read name; do
# skip the upstream of the main repo
[ $name == "$UPSTREAM" ] && continue
# Update this branch's .gitignore, and remember to push this commit.
# If we're switching remotes, clean out what we've done so far.
shortname=${name##refs/remotes/} # e.g., origin/master or r/feature/X
remote=${shortname%%/*} # e.g., origin or r
branch=${shortname#remote/} # e.g., master or feature/X
# if we're changing remotes, clear out the old one
[ $remote != $REMOTE ] && new_remote $remote
# switch detached HEAD to commit corresponding to remote-tracking name
git checkout -q $name || die "unable to check out $name"
# update .gitignore (but skip all this if it's correct)
cmp -s .gitignore $IFILE 2>/dev/null && continue
cp $IFILE .gitignore || die "unable to copy $IFILE to .gitignore"
git add .gitignore || die "unable to add .gitignore"
# UGH: terrible commit message below, please fix
git commit -q -m "update .gitignore" || die "unable to commit"
commit=$(git rev-parse HEAD) || die "failed to rev-parse HEAD"
# remember to push this commit (by hash ID) to refs/heads/$shortname
# on $REMOTE (which is correct because of new_remote above)
UPDATED="$UPDATED $commit:refs/heads/$shortname"
done < $TF
# push any accumulated commits, or do nothing if none accumulated
new_remote
# and we're done!
exit 0
推荐阅读
- asp.net - 从 Global.asax 执行 Javascript 函数
- python - 这个程序有什么问题?为什么我在以下 python 代码中出现运行时错误?
- php - Magento 2.3 库存管理,库存价值来自外部系统
- macos - 用于挂载网络卷的本机 API
- glsl - 将 ShaderToy Chromakey 示例移植到 P5.js
- scala - 无法在 IntelliJ IDEA 中运行 scala 项目
- c# - 包括工作,但加入不工作 c# linq
- javascript - 将 setState 钩子函数传递给子组件时 React Infinite 重新渲染
- codesniffer - 不鼓励类使用 PHPCS
- javascript - ZEIT 现在无服务器功能 - 带参数的路由