git создает удаленную ветку, не перекрывая ни одну из существующих веток - PullRequest
0 голосов
/ 16 мая 2018

Я хочу создать ветку разработки как локально, так и удаленно, без случайного вмешательства в чьи-либо ветки разработки.Создать локальную ветвь легко и не страдает от каких-либо условий гонки, но безопасно создать удаленную ветку сложно.

Скажем, я хочу создать ветку с именем cleanup, но кто-то еще мог иметь такую ​​жеИдея и создал свою собственную ветку с именем cleanup как раз передо мной.Если я просто git push --set-upstream origin cleanup, возможно, он создаст новую удаленную ветку, или, может быть, он ускорит пересылку уже существующей ветви.

Я хочу, чтобы git push ... потерпел неудачу, если удаленная ветвь уже существует, поэтому я могу выбратьдругое имя ветви.

Я уже знаю о нескольких несовершенных решениях, таких как git fetch, а затем быстро нажатие;это все еще зависит от условий гонки.Или сделать git push, заметив, существует ли удаленная ветвь в выводе команды, и затем попытаться восстановиться из нежелательной ситуации;это очень грязно и может привести к еще худшим условиям гонки во время восстановления.Также возможно использовать git push --force-with-lease различными способами, чтобы делать похожие вещи, но самым близким, к которому я пришел, было решение моей проблемы, отказавшийся создать ветку вместо быстрого перехода, что противоположно моей цели.

ОБНОВЛЕНИЕ : В идеале решение не должно иметь алгоритмической сложности, зависящей от количества файлов в дереве или длины истории мерзавцев.Решения, требующие создания нового клона репозитория или удаления и повторной проверки всех файлов в рабочем дереве, неприемлемы.

Ответы [ 3 ]

0 голосов
/ 17 мая 2018

Итак, у вас есть репо с постера, где все запускают WIP-коммиты, которыми стоит поделиться или что-то в этом роде.Существуют и другие рабочие процессы, которые не приводят к таким вопросам, как ваш, к запуску запросов на удаление и т.п., но у всех рабочих процессов есть свои недостатки.Масштабирование может быть недостатком для репозиториев, но избежать столкновений в пространстве имен несложно.

Имена ветвей репо-локальны.Любая переписка между репозиториями является исключительно вопросом удобства и сотрудничества;часто удобно использовать одни и те же имена, но также часто удобно иметь локальное имя для ветви или вообще не делиться им.

Продолжая то, что вы говорите, так и здесь.

git push -u origin featureb:thejoshwolfe/featureb

когда вы нажимаете ветку featureb в первый раз, я нажимаю :jthill/featureb и т. Д. Ваше репо принадлежит вам, ветви в ваших репозиториях отслеживают любые ветки вверх по течению, которые вы говорите.

edit: Git по умолчанию откажется от нажатий, которые не включают в себя всю текущую историю ветки.Если два человека попытаются выдвинуть противоречивые истории к одному имени ветви, git отклонит все, что придет вторым.Вы можете использовать --force-with-lease, чтобы делать безопасные форс-мажоры в больших объемах репозиториев на центральной и большой станциях, но есть и другие способы избежать этой проблемы.

Чтобы зарезервировать или отменить новое имя ветки, выполните

git push origin $(git commit-tree -m - `:|git mktree` <&-):refs/heads/newbranch

и если это сработает, то ваше имя, вы можете смело нажимать на него сейчас с git push -u origin +newbranch.

0 голосов
/ 18 мая 2018
function git-new-branch() {
    # usage: git-new-branch branchname
    git push origin $(git commit-tree -m "" $(git mktree <&-) <&-):refs/heads/$1 || return 1
    git checkout -b $1
    git push --set-upstream --force-with-lease origin $1
}

Объяснение

git mktree и git commit-tree читать stdin, поэтому используйте <%-, чтобы закрыть stdin для этих процессов.

git mktree является идемпотентом и печатаетsha1 пустого дерева.

git commit-tree делает пустой коммит с пустым деревом, и, что важно, коммит не имеет родителей.git commit-tree также печатает sha1, но он не идемпотентен.В коммите есть ваш user.name и метка времени.Кроме того, этот коммит попадает в каталог .git как сиротский коммит.В конечном итоге осиротевшие коммиты будут автоматически собираться мусором git;см git help gc.-m "" означает, что сообщение о фиксации будет пустым.Но ничего из этого не имеет большого значения, потому что этот коммит будет использован один раз и немедленно отменен.

Первый git push выдвигает пустой коммит к названию ветви, которое вы дадите.Если ветвь уже существует, она гарантированно будет отклонена, потому что пустой коммит не имеет родителей, поэтому его толчок не может вызвать ускоренную перемотку вперед.(А так как фиксация генерируется заново при каждом вызове, нет никаких шансов, что коммит уже находится где-нибудь в удаленном репо.)

|| return 1 прервет функцию в случае сбоя команды.

Нет ошибок при проверке команды git checkout -b.Эта функция предполагает, что у вас еще нет локальной ветки с заданным именем.

Вторая git push использует --force-with-lease, что означает, что удаленная ветвь будет обновлена ​​только в том случае, если она все еще такая, как мы думали.Это пустой коммит, который мы только что выдвинули.

Кредиты

Спасибо за ответы от o11c и jthill за вдохновение для этого решения.

0 голосов
/ 16 мая 2018

Создать ветку с новым корневым коммитом.Поскольку у корневых коммитов нет родителей, это не может быть перемотка вперед.

Затем используйте --force-with-lease, чтобы заменить временный корневой коммит реальным коммитом, который вы хотите.

Изменить: автономный скрипт, чтобы продемонстрировать, что это работает:

#!/bin/bash
set -e

# make this script idempotent
rm -rf /tmp/git-remote-demo/
mkdir -p /tmp/git-remote-demo/
cd /tmp/git-remote-demo/

# create a new "remote" repo and a couple of clones.
# (i.e what most real-world repos already look like)
git init --bare ./remote.git/
echo
git clone ./remote.git user1/
(cd user1/;
echo 'this is a readme' > readme.txt
git add readme.txt
git commit -m 'initial commit'
echo 'it has been modified' >> readme.txt
git add readme.txt
git commit -m 'modify readme'
git push origin master
)
echo
git clone ./remote.git user2/
echo

# then each user does work on their part
for user in user1 user2
do
(
    cd "$user"
    echo "I am $user"

    # work on a feature
    echo "work by $user" > readme.txt
    git add readme.txt
    git commit -m "work by $user"

    # interesting part starts here
    empty_tree="$(true | git mktree)"

    temp_commit="$(git commit-tree "$empty_tree" -m "temp commit for $user")"
    # demonstrate that this works even for race conditions
    git push origin "$temp_commit":refs/heads/race || echo "race push failed for $user"
    # demonstrate that this works normally
    { git push origin "$temp_commit":refs/heads/full && git push origin master:full --force-with-lease; } || echo "full push failed for $user"
    echo
)
done

echo "Remote branches:"
(cd remote.git && git branch -av)
...