один и тот же глобальный удаленный файл gitignore во всех филиалах - PullRequest
0 голосов
/ 30 декабря 2018

Я пытаюсь добиться того, чтобы у меня был один файл .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 в веткеmaster (или в отдельной ветви gitignore-branch), которая автоматически синхронизируется во всех ветвях (локально, а также удаленно при нажатии).
Проблема здесь в том, что я не хочу использовать git config --global core.excludesfile /path/to/local/.gitignore (как предложено здесь ), поскольку я хочу, чтобы мои партнеры по проекту также использовали этот конкретный файл .gitignore и не должны были изменять файл git config для этого.В этом комментарии кто-то еще задает этот вопрос, но на него не было ответа.Я также не могу найти ответы на вопросы о переполнении стека.

Краткое резюме
Я хочу отредактировать файл .gitignore только на одной ветви и синхронизировать это изменение со всеми другими ветвями (автоматически) эффективным способом времени и усилий.После этого я хочу отправить изменения во всех ветвях в удаленный репозиторий (желательно только с одной или несколькими строками кода и без необходимости переделывать коммит с соответствующим сообщением коммита для каждой ветки).

1 Ответ

0 голосов
/ 30 декабря 2018

К сожалению, пока .gitignore (или даже любой файл) отслеживается (что означает в индексе ), логически отдельная копия этого файла переходит в каждый коммит вы делаете.В результате этого невозможно достичь того, чего вы хотите.

Самое близкое, что вы можете сделать, как упомянул phd , хранить в каждом новом коммите .gitignoreзапись типа символьная ссылка (режим 120000 в Git-internal-ese).Тогда, даже если у каждого коммита есть логически отдельная (возможно, физически общая) копия целевого пути пути ссылки, когда Git будет читать содержимое .gitignore, он будет читать содержимое целевого пути, а не .gitignore файл рабочего дерева, который был только что скопирован из любого коммита, который вы указали git checkout, чтобы выйти.

Однако вы можете автоматизировать процесс обновления .gitignore файлов при нескольких фиксациях.Самый простой способ сделать это, вероятно, использовать git worktree add для создания отдельного рабочего дерева, в котором будут выполняться обновления.Предполагается, что ваша версия Git по крайней мере 2.5, а лучше 2.15 (чтобы избежать ошибок в git worktree).

Ниже приведен полностью непроверенный сценарий, который для каждой ветви удаленного отслеживания гарантирует, чтоКоммит tip этой ветви удаленного отслеживания содержит .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
...