Почему «git pull --rebase» переопределяет локальные фиксации, когда push и pull нацелены на разные репозитории?

Описание

У меня простая настройка, в которой мой локальный репозиторий имеет одну ветку main.

Эта ветка main настроена для:

  • Всегда извлекайте (перебазируйте) изменения из ветки main в моем общедоступном репозитории GitHub.
  • Всегда отправляйте изменения в ветку main моего частного репозитория GitHub.

Почему?

Эта установка не позволяет мне:

  • Утечка любых данных компании по ошибке.
  • Загрязнение личных файлов конфигурации, которые я использую повсюду.

Проблема

По какой-то причине при такой настройке git pull --rebase всегда переопределяет мои локальные коммиты теми, которые были взяты из личного общедоступного репозитория. Он правильно fetch фиксирует изменения в локальной ветке удаленного отслеживания, но на самом деле не перебазирует соответствующую ветку. Это просто переопределяет это.

Я хотел бы знать, это git ошибка или я делаю что-то не так?

Минимальные шаги воспроизведения

Мне удалось воспроизвести проблему с локальными репозиториями, выполнив следующие действия:

mkdir /tmp/repo_1 /tmp/repo_2

# Initialize first repository with a single commit that creates file "a".
git -C /tmp/repo_1 init --quiet
touch /tmp/repo_1/a
git -C /tmp/repo_1 add a
git -C /tmp/repo_1 commit --quiet --message "create file a"

# Initialize second repository which will be used for pushing.
git -C /tmp/repo_2 init --bare --quiet

# Initialize local repository and configure it to pull from "repo_1" and push to "repo_2"
git clone /tmp/repo_1 /tmp/local --quiet
git -C /tmp/local remote set-url --push origin /tmp/repo_2

# Make second commit in a local repository (first one was cloned from "repo_1") that creates file "b".
touch /tmp/local/b
git -C /tmp/local add b
git -C /tmp/local commit --quiet --message "create file b"

# Push both commits to "repo_2".
git -C /tmp/local push --quiet

# Pull --rebase changes from "repo_1" and notice that second commit is now missing.
echo "Local commits before pull --rebase:"
git -C /tmp/local log --oneline
echo
git -C /tmp/local pull --rebase --quiet
echo "Local commits after pull --rebase:"
git -C /tmp/local log --oneline

Обратите внимание, что замена последнего абзаца ручными командами fetch + rebase дает ожидаемый результат:

# Do manual fetch + rebase and notice that it works as expected.
echo "Local commits before fetch + rebase:"
git -C /tmp/local log --oneline
echo
git -C /tmp/local fetch --quiet
git -C /tmp/local rebase --quiet origin/master
echo "Local commits after fetch + rebase:"
git -C /tmp/local log --oneline

Git-версия: 2.45.2

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
1
107
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я решил написать более прямой ответ вместо того, чтобы просто указывать на ссылки в моем комментарии выше.

# Push both commits to "repo_2".
git -C /tmp/local push --quiet

Когда OP неявно нажимает на repo_2, они продвигают удаленную ветку «main» на repo_2 и локальную ветку удаленного отслеживания «origin/main». По замыслу «repo_1» не получает коммитов, а удаленная ветка «main» в repo_1 остается прежней.

$ git -C /tmp/repo_1 log --oneline 
2b75abd (HEAD -> main) create file a

$ git -C /tmp/repo_2 log --oneline
f74a217 (HEAD -> main) create file b
2b75abd create file a

$ git -C /tmp/local log --oneline
f74a217 (HEAD -> main, origin/main, origin/HEAD) create file b
2b75abd create file a

На этом этапе, если бы мы выполнили «git fetch» ​​(неявно из repo_1), мы увидели бы сообщение о принудительном обновлении в ветке удаленного отслеживания «origin/main», поскольку оно будет принудительно возвращено к исходному коммиту ( сообщение фиксации «создать файл a»). Однако ручное перебазирование локальной «main» на удаленную ветку отслеживания «origin/main» на этом этапе дало бы желаемый эффект. Однако вместо ручной выборки и перебазирования предположим, что мы используем «git pull --rebase».

# Pull --rebase changes from "repo_1" and notice that second commit is now missing.

'git pull --rebase' имеет специальную логику, предназначенную для распознавания перезаписи удаленной ветки (например, перебазирования). Это упоминается в опции «-rebase» справки «git pull»:

Если существует ветвь удаленного отслеживания, соответствующая восходящему ветвь и восходящая ветвь были перебазированы с момента последней выборки, rebase использует эту информацию, чтобы избежать переноса нелокальных изменений.

В этом случае специальная логика срабатывает из-за того, что удаленная ветка «main» в repo_1 («создать файл a»), которая обычно синхронизируется с веткой удаленного отслеживания или опережает ее, вместо этого находится позади удаленной ветки отслеживания. ветка «origin/main» («создать файл b»).

Эта специальная логика пытается идентифицировать «локально созданные» коммиты и старые коммиты, которые, по-видимому, раньше находились на удаленном компьютере, но были заменены перезаписью/перебазированием. Он делает это, просматривая ветку удаленного отслеживания «origin/main», которая указывает на фиксацию «создать файл b», и после этого не видит никаких коммитов. Похоже, это указывает на то, что удаленная ветка «main» в repo_1 была намеренно перебазирована/переписана, чтобы удалить фиксацию «создать файл b», поэтому origin/main сбрасывается для фиксации «создать файл a». Поскольку логика не выявила локальных коммитов для перебазирования, «main» также сбрасывается на «origin/main».

$ git -C /tmp/repo_1 log --oneline 
2b75abd (HEAD -> main) create file a

$ git -C /tmp/local log --oneline
f74a217 (HEAD -> main, origin/main, origin/HEAD) create file b
2b75abd create file a

$ git -C /tmp/local pull          
From /tmp/repo_1
 + f74a217...2b75abd main       -> origin/main  (forced update)
Successfully rebased and updated refs/heads/main.

$ git -C /tmp/local log --oneline
2b75abd (HEAD -> main, origin/main, origin/HEAD) create file a

Я думаю, что обычный вариант использования для указания другого удаленного push-управления заключается в том, что при извлечении и отправке с удаленного устройства необходимо использовать разные протоколы для доступа к одному и тому же репо. Я не уверен, есть ли какие-либо другие ошибки в git, которые могут появиться, когда URL-адреса для извлечения и отправки фактически указывают на разные репозитории, синхронизация которых не гарантируется.

Другой способ выполнить то, что делает OP, — использовать два отдельных пульта для repo_1 и repo_2, а затем отключить отправку в repo_1 либо на сервере, либо на клиенте (возможно, установив для push-URL для repo_1 фиктивное значение). Тогда 'git log --all --decorate' будет точно отражать состояние каждого пульта. Затем можно всегда извлечь данные из обоих репозиториев (git fetch --all --prune), а затем убедиться, что локальные ветки созданы и связаны с удаленными ветвями отслеживания для repo_2, например. git checkout --track repo_1/feature; Функция git push -u repo2. Затем можно было бы извлечь коммиты из веток repo_1 в локальную ветку, например. 'git merge --ff-only repo_1/main'.


Больше информации:

Блин, очень хорошее объяснение и ссылки на ссылки, спасибо! Я знаю об удаленной настройке origin/upstream, но при этом приходится постоянно их указывать. Когда я узнал, что могу предотвратить отправку в репозиторий компании, как вы также предложили, установив URL-адрес push-уведомления на какую-то ерунду, я подумал Why not just specify explicit push and pull for "same" origin?, и вот мы здесь :D Думаю, я пока пойду вручную fetch + rebase, если только я не столкнетесь с новыми неприятностями на пути.

Iskustvo 29.07.2024 13:23

Другие вопросы по теме

Похожие вопросы