Как я могу иметь 2 независимых локальных филиала Git от той же удаленной ветви - PullRequest
0 голосов
/ 24 декабря 2018

У меня есть основная ветка, и я работаю над двумя функциями, которые включают одни и те же файлы.Я хочу иметь 2 локальные ветви, указывающие на один и тот же основной канал, но с разными изменениями.Я не хочу фиксировать изменения локально, чтобы в IDE сохранялось форматирование, например, оттенки границ в
https://d3nmt5vlzunoa1.cloudfront.net/idea/files/2018/10/k8sCompletion.png

Я не смог успешно использовать git checkout, потому что когда я вносил изменения в один и переключалв другой ветке на нем тоже видны неустановленные изменения.Решение, которое я нашел, заключается в проверке моего кода в 2 репозиториях, потому что git worktree, похоже, требует 2 разных удаленных веток.Однако это означает неэффективность жесткого диска.Есть ли способ достичь того, чего я хочу?

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

1 Ответ

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

TL; DR: ваша проблема на самом деле довольно тривиальна, при условии, что ваш Git по крайней мере версии 2.15: просто используйте git worktree add правильно, создавая две ветви, которые используют то же имя удаленного слежения, что и их восходящий поток.* Если нет, ваш метод использования двух репозиториев, возможно, является лучшим.Вы все еще можете использовать git worktree add для версий от 2,5 до 2,15, если вы избежите одной серьезной проблемы (о которой я расскажу ниже).

Long

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

Это ожидание не поддерживается Git.

РеальноеПроблема здесь в том, что не существует такой вещи, как «неустановленные изменения», и не существует такой вещи, как «поэтапные изменения».То, что вы видите, является иллюзией, созданной на лету, потому что иллюзия имеет тенденцию быть более полезной для программиста-человека.Что Git показывает вам как изменения вычисляются по требованию, сравнивая два из трех элементов: текущий коммит , index и work-дерево .В действительности, однако, есть просто файлов , хранящихся в рабочем дереве и в индексе, которые являются непостоянными и изменяемыми;плюс коммитов , хранящихся в хранилище, которые являются постоянными - ну, в основном, постоянными - и заморожены на все времена.См. Мой недавний ответ на Почему выходные данные в git diff отличаются от git diff --staged? , чтобы узнать больше об этом.

В хранилище (потенциально) много коммитов, ноВ каждом репозитории имеется только одна (1) пара рабочее дерево + индекс. 1 Вы можете добавить больше пар индексное и рабочее дерево, используя git worktree add, который вы пробовали.Это должно хорошо работать в вашем случае, если ваш Git по крайней мере версии 2.15 (от Git 2.5 и выше, но не включая Git 2.15, git worktree add имеет потенциально серьезную ошибку, в зависимости от того, как именно вы ее используете).


1 Пустой репозиторий (созданный с помощью git clone --bare или git init --bare) имеет один индекс и нет рабочего дерева, но, кажется, можно предположить, чточто вы не работаете с пустым хранилищем.


... git worktree, похоже, требуется 2 разных удаленных ветки

Это не так.

Что делает git worktree add, так это добавляет пару индекс-и-дерево-работа.Добавленное рабочее дерево находится в отдельном каталоге рабочего дерева (основной каталог рабочего дерева находится прямо рядом с каталогом .git основного репозитория; каталог .git содержит все индексы вместе со всемидругой вспомогательной информации, необходимой Git).Добавленное рабочее дерево также имеет свой собственный HEAD, но разделяет все имена ветвей и имена удаленного отслеживания .

Ограничение на git worktree add подразумевает, что каждое рабочее дерево должно использовать свое имя ветви или вообще не использовать имя для его HEAD.Чтобы правильно определить, как это работает, нам понадобится отступление от HEAD и имена ветвей.Я вернусь к этому через мгновение, но сначала давайте

Примечание: не существует такой вещи, как удаленная ветвь .У Git есть термин, который он называет имена веток удаленного отслеживания .Теперь я предпочитаю называть эти имена для удаленного слежения , поскольку в них отсутствует одно важное свойство, которым обладают имена ветвей. имя для удаленного отслеживания обычно выглядит как origin/master или origin/develop, то есть имя, которое начинается с origin/. 2


2 Вы можете определить более одного пульта или изменить имя по умолчанию того пульта, которое у вас уже может быть, на что-то отличное от origin.Например, вы можете добавить второй пульт с именем upstream.В этом случае у вас также могут быть upstream/master и / или upstream/develop.Все это допустимые сокращенные формы имен для удаленного отслеживания.


Фиксы, имена ветвей и HEAD

Единицей постоянного хранения в любом репозитории Git является коммит.Коммиты, как вы уже видели, идентифицируются по большому, уродливому, по-видимому, случайному (вовсе не случайному) уникальному для каждого коммиту хэш-идентификатору , как 5d826e972970a784bd7a7bdf587512510097b8c7.Эти вещи бесполезны для людей, и мы обычно используем их только посредством вырезания и вставки или косвенно, но идентификаторы хеша - это настоящие имена.Если у вас есть 5d826e972970a784bd7a7bdf587512510097b8c7 (коммит в Git-репозитории для Git), это всегда этот конкретный коммит.Если у вас его нет, вы можете получить копию Git-репозитория для Git (или обновить существующую копию), и теперь у вас есть do , а , что commit- а это версия Git 2.20.(Имя v2.20.0 является более ориентированным на человека именем для этого коммита, и это то, что мы обычно используем. Git хранит таблицу перевода имен тегов в хэш-идентификаторы, как v2.20.0 становится человеческим.читаемое имя для этого коммита.)

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

Таким образом, каждый коммит, когда-либо сделанный в репозитории, соединяется с более ранними коммитами в этом же репозитории.,Это история в хранилище: строка коммитов, начинающаяся в конце и работающая в обратном направлении.В очень простых случаях, например, в довольно новом репозитории, у нас может быть несколько коммитов в очень простой строке, подобной этой:

A <-B <-C

Здесь заглавные буквы заменяют фактические хеш-идентификаторы (которые, помните, большие, некрасивые и, очевидно, случайные).То, что мы - и / или Git - будем делать, это начинать с end , с коммита C и работать назад .Commit C хранит фактический хэш-идентификатор commit его родителя B, поэтому из C мы можем найти B.Между тем B хранит хэш-идентификатор родителя A.Поскольку A является самым первым коммитом, у него нет родителя, и вот как Git сообщает, что мы достигли начала истории: идти некуда.

Хитрость,тем не менее, нам нужно найти commit C, чей хэш-идентификатор явно случайный.Это где имена ветвей . Мы выбираем имя наподобие master и используем его для хранения фактического хеш-идентификатора C:

A <-B <-C   <--master

Мы упоминали ранее о коммитахпосле того, как сделано, никогда не может измениться.Это означает, что нам не нужно рисовать все внутренние стрелки: мы знаем, что коммит не может помнить своих потомков, потому что они не существуют, когда мы делаем коммит, но коммит может запомнитьего родитель, потому что родитель действительно существует в то время.Git навсегда заморозит родительский хеш в новый коммит.Поэтому, если мы хотим добавить новый коммит в нашу строку из трех A-B-C, мы просто сделаем это:

A--B--C--D

Чтобы запомнить хэш-идентификатор D, Git немедленно записывает новый идентификатор хеша коммита в имени master:

A--B--C--D   <-- master

Так что коммиты фиксируются на все времена, но имена ветвей перемещаются,все время!

Теперь предположим, что мы добавляем новое имя ветви, develop.Имя ветви, в Git, должно указывать на ровно один коммит .Один коммит, на который мы хотели бы указать, вероятно, самый последний, D:

A--B--C--D   <-- develop, master

Обратите внимание, что оба имени указывают на один и тот же коммит .Это совершенно нормально!Все четыре коммита находятся на обеих ветвях.

Теперь давайте добавим новый коммит и назовем его E:

A--B--C--D
          \
           E

Какое из двух имен ветвей должноу нас есть Git обновление?Это где HEAD входит.

Beпрежде чем мы сделаем E, мы сообщим Git , какое имя добавить HEAD к .Мы делаем это с git checkout.Если мы git checkout master, Git прикрепит HEAD к имени master.Если мы git checkout develop, Git прикрепит HEAD к имени develop.Давайте сделаем последнее до того, как мы создадим E, поэтому мы начнем с:

A--B--C--D   <-- develop (HEAD), master

Теперь мы сделаем E, и Git обновит имя, к которому присоединен HEAD, т.е., develop:

A--B--C--D   <-- master
          \
           E   <-- develop (HEAD)

Короче говоря, как растут ветви.Git создает новый коммит, чьим родителем является текущий коммит, найденный через имя HEAD, которое присоединяется к какому-либо имени ветви.После создания нового коммита - который дает ему новый, уникальный, большой уродливый хеш-идентификатор - Git записывает новый хеш-код нового коммита в то же имя ветви, так что имя ветки теперь указывает на новый коммит.Новый коммит продолжает указывать на старый коммит.

Добавленные рабочие деревья требуют, чтобы вы прикрепляли их заголовки к разным ветвям

По причинам, которые будут иметь смысл через мгновение, git worktree addтребует, чтобы только что добавленное рабочее дерево использовало другое имя ветви для HEAD этого рабочего дерева.То есть, когда мы рисуем имена коммитов и веток и присоединяем HEAD к какому-либо имени ветки, мы действительно присоединяем этого рабочего дерева HEAD, поскольку теперь существует более одного HEAD.

Итак, теперь у нас есть два имени, master и develop, и мы можем создать два разных рабочих дерева, используя эти два разных имени ветви:

A--B--C--D   <-- master (HEAD)    # in work-tree M
          \
           E   <-- develop

против:

A--B--C--D   <-- master
          \
           E   <-- develop (HEAD)  # in work-tree D

Содержимое рабочего дерева и его индекс, как правило, начинают совпадать с данными его HEAD коммита.Мы изменим некоторые файлы в рабочем дереве, git add их в индекс этого рабочего дерева, и git commit там, и обновим этого рабочего дерева HEAD.Это , почему эти два должны использовать разные имена веток.Посмотрите, что происходит, когда мы работаем внутри рабочего дерева M (для мастера).Мы начинаем с:

A--B--C--D   <-- master (HEAD)    # in work-tree M
          \
           E   <-- develop

Индекс и соответствие рабочего дерева коммит D.Мы делаем некоторую работу git add и git commit, чтобы сделать новый коммит.Новый хэш-идентификатор нового коммита является новым и уникальным;давайте назовем его F здесь и нарисуем, обновив имя master:

A--B--C--D--F   <-- master (HEAD)    # in work-tree M
          \
           E   <-- develop

Теперь давайте перейдем к другому дереву работ (D для разработки, но это звучит очень похоже наcommit D, так что давайте просто перестанем называть это так).Это имеет свой собственный HEAD, поэтому картинка выглядит так:

A--B--C--D--F   <-- master
          \
           E   <-- develop (HEAD)

Обратите внимание, что master изменился - имена ветвей совместно используются всеми рабочими деревьями - и новый коммитF появился, так как коммиты также доступны.Но develop все еще указывает на фиксацию E, и наши индекс и рабочее дерево здесь, в этом рабочем дереве, совпадают с E.Теперь мы изменим некоторые файлы, git add, чтобы скопировать их обратно в индекс, и git commit, чтобы сделать новый коммит, который мы можем назвать G:

A--B--C--D--F   <-- master
          \
           E--G   <-- develop (HEAD)

Commit G появится вдругое рабочее дерево и другое рабочее дерево develop будут идентифицировать commit G, но, поскольку другое рабочее дерево имеет master / commit F извлечено, индекс и работа другого рабочего дерева-tree будет по-прежнему соответствовать фиксации F.

Настройка восходящего потока для любого имени ветви - это то, что вы контролируете

Когда вы создаете новое имя ветви, используя либо git checkout -b или git branch, вы управляете:

  • , имеет ли эта новая ветвь какую-либо восходящую настройку, и
  • , если да, какое имя - origin/whateverтипично, но это может быть любое имя - хранится в этом параметре.

Для вашего master вполне нормально использовать origin/master в качестве имени восходящего потока, идля вашего develop, чтобы использовать origin/develop в качестве имени восходящего потока, но здесь нет никаких ограничений.Например, вы можете иметь все ваши ветви поделиться origin/master в качестве их восходящего потока.Или, у вас могут быть ветви, которые имеют no upstream set.См. Почему я должен "git push --set-upstream origin "? для обсуждения настроек восходящего потока.

Существует волшебное значение по умолчанию:

$ git checkout feature-xyz

будет тпопробуйте проверить существующую ветку feature-xyz.Если не является веткой feature-xyz, ваш Git проверит все ваши имена для удаленного отслеживания, чтобы выяснить, есть ли, например, origin/feature-xyz.Если это так, ваш Git создаст ваш собственный feature-xyz, указывая на такой же коммит , что и origin/feature-xyz, и с origin/feature-xyz, установленным в качестве восходящего.Это сделано для удобства.Если это неудобно, не используйте его: используйте -b.

Команда git worktree add делится этим конкретным трюком с git checkout: у обоих есть -b, который создает new ветвь (без этого), и оба по умолчанию пытаются найти какую-то существующую ветвь.Таким образом, оба автоматически создадут новую ветвь с восходящим набором, для этого конкретного случая.

Отдельные HEAD и добавленные индексы, а также ошибка в Git 2.5 через (но не включая) 2.15

InGit, detached HEAD просто означает, что HEAD не привязан к имени ветки.Помните, что обычный способ нарисовать то, что происходит, это прикрепить HEAD к какому-либо имени:

...--F--G--H   <-- master (HEAD)

Вместо этого мы можем сделать точку Git HEAD непосредственно для коммита без указания имени ветви:

...--F--G   <-- HEAD
         \
          H   <-- master

Когда в этом режиме мы делаем новый коммит, Git записывает хеш-код нового коммита в сам HEAD, а неимя, к которому HEAD не привязан:

...--F--G--I   <-- HEAD
         \
          H   <-- master

Добавленное рабочее дерево может всегда находиться в режиме отсоединенного HEAD, но в версиях 2.5 Git есть ужасная ошибка,где git worktree был впервые представлен, это не исправлено до версии Git 2.15.

В частности, каждое добавленное рабочее дерево имеет свой собственный HEAD и свой собственный закрытый индексный файл.Это необходимо из-за того, как работает остальная часть Git: HEAD записывает информацию о этом рабочем дереве, а индекс является индексом для этого рабочего дерева, поэтому онивсе один большой элемент группы.К сожалению, сборщик мусора в Git , git gc не был должным образом учен уважать добавленные рабочие деревья.

Задача сборщика мусора - найти не связанную (неиспользованную /ненужные) объекты Git - BLOB-объекты, деревья, коммиты и аннотированные теги, которые выглядят как остатки мусора в хранилище.Git использует это так, что команды Git могут в любое время создавать эти различные внутренние объекты, не беспокоясь о том, необходимы ли они действительно , и не предпринимая каких-либо специальных действий для обработки прерывания (например, путем, CTRL + C или отключение сетевого сеанса).Другие обычные ежедневные действия Git, включая git rebase, могут генерировать этот мусор.Это все совершенно нормально и нормально, потому что уборщик, git gc, регулярно очищает его.

Но любой новый коммит, который вы делаете с отсоединенной HEAD, имеет только HEAD, сам ссылающийся наих.В рабочем дереве main это не проблема: уборщик gc проверяет файл HEAD, видит ссылку и знает, что не нужно удалять эти коммиты.Но git gc не проверяет добавленные дополнительные HEAD.Поэтому, если у вас есть добавленное рабочее дерево с отсоединенным HEAD, объекты этого отсоединенного HEAD могут исчезнуть.Аналогичные правила применяются к объектам BLOB-объектов, и если на объект BLOB-объекта, сохраненный в индексе добавленного рабочего дерева, ссылаются * только из этого индекса, git gc может удалить базовый объект BLOB-объекта.

Естьвторичная защита: git gc по умолчанию не удаляет объекты моложе 14 дней.Это дает всем командам Git 14 дней на выполнение своей работы, прежде чем придет уборщик и выбросит их незавершенные объекты в мусорное ведро за офисом.Так что все это прекрасно работает в основном рабочем дереве и прекрасно работает в добавленных рабочих деревьях в Git 2.15 и более поздних версиях.Но для промежуточных версий Git может появиться git gc, увидеть 14-дневный коммит, дерево или блоб, которые не следует выбрасывать из-за добавленного рабочего дерева, ипока не осознаешь этого и выброси.

Эта ошибка не возникает, если у вас нет отдельного HEAD и вы хотите добавить и зафиксировать его в течение 14 дней.Это также не удивительно, если вы отключите сборку мусора, но это, как правило, не очень хорошая идея: Git зависит от gc для очистки и поддержания хорошей производительности.И, конечно же, это было исправлено в Git 2.15, так что если у вас это или позже, все в порядке.Он влияет только на добавленные рабочие деревья, поэтому соблюдайте осторожность в диапазоне от 2,5 до 2,15.

...