跨所有分支的同一全局远程gitignore文件

时间:2018-12-30 10:42:26

标签: git github 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-branch分支中)拥有一个.gitignore文件,该文件会在所有分支之间自动同步(本地以及在按下时在远程)。
这里的问题是我不想使用git config --global core.excludesfile /path/to/local/.gitignore (如建议的here),因为我希望我的项目合作伙伴也使用该特定的.gitignore文件,而不必更改git config为此文件。在this comment中,有人在问这个问题,但尚未得到回答。我也找不到关于我的问题的堆栈溢出问题的任何答案。

简短摘要
我只想在一个分支上编辑.gitignore文件,并以省时省力的方式使更改与所有其他分支(自动)同步。之后,我想将所有分支中的更改推送到远程存储库(最好只用一行或几行代码,而不必为每个分支重新生成带有相应提交消息的提交)。

1 个答案:

答案 0 :(得分:2)

不幸的是,只要跟踪.gitignore(或实际上任何文件)(即索引中的 ),该文件在逻辑上都是分开的进入您所做的每次提交。这样做的结果是无法实现您想要的。

您能找到的最接近的phd mentioned,是在每个新提交中存储.gitignore条目,该条目的类型为符号链接(在Git-internal- ese)。然后,即使每个提交都有链接的目标路径名的逻辑上独立(可能是物理共享)的副本,当Git读取120000的内容时,它将读取目标路径名的内容,而不是目标路径名的内容。一个.gitignore工作树文件,它只是从您告诉.gitignore退出的所有提交中复制而来。

但是,您可以自动跨多个提交更新git checkout文件的过程。最简单的方法可能是使用.gitignore创建用于进行更新的单独工作树。假设您的Git版本至少为2.5,最好至少为2.15(以避免git worktree add中的错误)。

以下是一个完全未经测试的脚本,对于每个远程跟踪分支,该脚本将确保该远程跟踪分支的尖端提交包含与主分支中当前分支中的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