-
Коммит в git репозитории хранит снимок всех файлов в директории = огромная копия, только лучше.
-
Git пытается быть лёгким и быстрым насколько это только возможно, так что он не просто слепо копирует всю директорию каждый раз, а ужимает (когда это возможно) коммит в набор изменений или «дельту» = между текущей версией и предыдущей.
-
Git хранит всю историю о том, когда какой коммит был сделан. Вот почему большинство коммитов имеют предков - мы указываем на предков стрелками при визуализации. Поддержка истории коммитов более чем важна для всех, кто работает над проектом.
-
Визуализация небольшого git репозитория.
-
Сейчас в нём два коммита: первый, исходный коммит С0 и один коммит С1 после него, содержащий изменения.
- Пропишем команду:
git commit
Результат:
- Только что внесли изменения в репозиторий и сохранили их как коммит. У коммита, который мы только что сделали, есть родитель, С1, который указывает на предыдущий коммит.
Дано:
- Сделайте 2 коммита, чтобы получить визулизацию, как на картинке.
- Пропишем в командной строке:
git commit
git commit
Результат:
-
Ветки в Git, как и коммиты, невероятно легковесны. Это просто ссылки на определённый коммит — ничего более. Вот почему многие фанаты Git повторяют мантру: "делай ветки сразу, делай ветки часто".
-
Cоздание множества веток никак не отражается на памяти или жестком диске, удобнее и проще разбивать свою работу на много маленьких веток, чем хранить все изменения в одной огромной ветке.
-
Cозданная ветка хранит изменения текущего коммита и всех его родителей.
Создадим новую ветку с именем newImage.
- Пропишем команду (создание ветки):
git branch newImage
Результат:
- Ветка newImage указывает на коммит С1.
- Теперь сделаем коммит. Пропишем команду:
git commit
Результат:
- Ветка main сдвинулась, тогда как ветка newImage - нет. Всё из-за того, что мы не переключились на новую ветку, а остались в старой, о чём говорит звёздочка около ветки main.
- Чтобы сделать коммит для ветки newImage, нужно переключиься на ветку, а затем сделать коммит. Вернёмся к пукнту 1:
- Пропишем 2 команды:
git checkout newImage
git commit
Результат:
Дано:
-
Создай ветку с именем bugFix и переключись на неё.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команду:
git checkout -b bugFix
- Создаст ветку и переключиться на неё.
Результат:
-
Как объединять изменения из двух разных веток.
- Очень удобно создать ветку, сделать свою часть работы в ней и потом объединить изменения из своей ветки с общими.
-
Первый способ объединения изменений, который мы рассмотрим - git merge - слияние.
- Слияния в Git создают особый вид коммита, который имеет сразу двух родителей. Коммит с двумя родителями обычно означает, что мы хотим объединить изменения из одного коммита с другим коммитом и всеми их родительскими коммитами.
-
Две ветки, каждая содержит по одному уникальному коммиту.
-
Это означает, что ни одна из веток не содержит полный набор "работ", выполненных в этом репозитории.
-
Можно исправить эту ситуацию, выполнив слияние.
- Пропишем комвнду (слияние bugFix в main):
git merge bugFix
Результат:
-
Ветка main теперь указывает на коммит, у которого два родителя.
-
Если проследовать по стрелкам от этого коммита, вы пройдёте через каждый коммит в дереве прямиком к началу.
-
Это означает, что теперь в ветке main содержатся все изменения репозитория.
-
У каждой ветки — свой цвет. Каждый коммит становится того цвета, какого его ветка.
-
Если в нём изменения сразу двух веток - он становится цветом, смешанным из цветов родительских веток.
-
Цвет ветки main подмешан к каждому коммиту, а ветки bugFix - нет.
-
Cделаем слияние ветки main в ветку bugFix.
- Пропишем комвнды:
git checkоut bugFix
git merge main
Результат:
-
Так как ветка bugFix была предшественницей main, Git не делал ничего, только сдвинул bugFix на тот же коммит, где находится main.
-
Теперь все коммиты одного цвета, что означает, что каждая ветка содержит все изменения репозитория.
Дано:
-
Создай новую ветку под названием bugFix.
-
Переключись на новую ветку bugFix командой git checkout bugFix.
-
Сделай один коммит.
-
Вернись на ветку main при помощи git checkout.
-
Сделай ещё один коммит.
-
Слей ветку bugFix с веткой main при помощи git merge.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git checkout -b bugFix
git commit
git checkout main
git commit
git merge bugFix
Результат:
-
Oбъединения изменений в ветках - это rebasing. При ребейзе Git по сути копирует набор коммитов и переносит их в другое место.
-
Преимущество rebase в том, что c его помощью можно делать чистые и красивые линейные последовательности коммитов. История коммитов будет чище, если вы применяете rebase.
-
Две ветки.
-
Выбрана ветка bugFix (отмечена звёздочкой).
-
Сдвинуть наши изменения из bugFix прямо на вершину ветки main.
-
Благодаря этому всё будет выглядеть, как будто эти изменения делались последовательно, хотя на самом деле - параллельно.
- Пропишем командцу:
git rebase main
Результат:
-
изменения из bugFix находятся в конце ветки main и являют собой линейную последовательность коммитов.
-
С3' - это его "копия" в ветке main.
- Ветка main не обновлена до последних изменений
- Пропишем команду:
git checkout main
Результат:
- Пропишем команду:
git rebase bugFix
Результат:
- Так как ветка main был предком bugFix, git просто сдвинул ссылку на main вперёд.
Дано:
-
Переключись на ветку bugFix.
-
Сделай коммит.
-
Вернись на main и сделай коммит ещё раз.
-
Переключись на bugFix и сделай rebase на main.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git checkout -b bugFix
git commit
git checkout main
git commit
git checkout bugFix
git rebase main
Результат:
-
HEAD - это символическое имя текущего выбранного коммита — это, по сути, тот коммит, над которым мы в данный момент работаем.
-
HEAD всегда указывает на последний коммит из вашего локального дерева.
-
Большинство команд Git, изменяющих рабочее дерево, начнут с изменения HEAD.
-
Обычно HEAD указывает на имя ветки (например, main). Когда вы делаете коммит, статус ветки main меняется и это изменение видно через HEAD.
Смотрим, где находится HEAD до коммита и после при выполненных командах (в решении).
ДО:
- Пропишем команды:
git checkout C1
git checkout main
git commit
git checkout C2
ПОСЛЕ:
- HEAD скрывался за ветке main.
-
Отделение HEAD = присвоение не ветке, а конкретному коммиту.
-
Нужно для того, чтобы временно работать с конкретным коммитом, не привязывая его к основной линии разработки, что позволяет экспериментировать без риска потерять данные.
-
Новые коммиты, сделанные в этом состоянии, будут "осиротевшими", так как они не принадлежат ни одной ветке, и их сложнее найти.
Зачем это нужно ?
-
Изучение истории: позволяет посмотреть на состояние проекта в прошлом, например, чтобы найти ошибку или изучить код определенного коммита.
-
Временные эксперименты: Дает возможность проверить какой-либо код или исправление, не создавая новую ветку. Если эксперимент окажется неудачным, просто переключитесь обратно на нужную ветку, и "осиротевшие" коммиты будут проигнорированы, пока их не удалит сборщик мусора Git.
Когда следует быть осторожным ?
-
Создание коммитов: если вы создадите новые коммиты в состоянии "отсоединенного HEAD", они не будут прикреплены к какой-либо ветке. Это может привести к их потере.
-
Потеря данных: Это может быть опасно, если вы забудете, что находитесь в "отсоединенном" состоянии, и случайно сделаете коммиты. Чтобы избежать этого, лучше сразу создать новую ветку для любых изменений.
ДО:
- HEAD -> main -> C1.
- Пропишем команду:
git checkout C1
Результат:
ПОСЛЕ:
- HEAD -> C1.
Дано:
-
Отделим HEAD от ветки bugFix и присвоим его последнему коммиту в этой же ветке.
-
Укажи коммит при помощи его идентификатора (hash). Hash для каждого коммита указан в кружке на схеме.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команду:
git checkout C4
Результат:
-
Передвигаться по дереву Git при помощи указания хешей коммитов так себе.
-
В реальной ситуации у вас вряд ли будет красивая визуализация дерева в терминале, так что придётся каждый раз использовать git log = чтобы найти хеш нужного коммита.
-
Более того, хеши в реальном репозитории Git намного более длинные. Например, хеш для коммита, который приведён в предыдущем уровне - fed2da64c0efc5293610bdd892f82a58e8cbc5d8.
-
Git достаточно умён в работе с хешами. Ему нужны лишь первые несколько символов для того, чтобы идентифицировать конкретный коммит. Так что можно написать просто fed2 вместо колбасы выше.
С относительными ссылками можно начать с какого-либо удобного места (например, с ветки bugFix или от HEAD) и двигаться от него.
-
Относительные ссылки - мощный инструмент, но мы покажем два простых способа использования:
-
Перемещение на один коммит назад = ^.
-
Перемещение на несколько коммитов назад = ~.
-
- Когда мы добавляем ^ к имени ссылки, Git воспринимает это как указание найти родителя указанного коммита.
-
Так что main^ означает "первый родитель ветки main".
-
main^^ означает прародитель (родитель родителя) main
- Давайте переключимся на коммит Выше main
- Пропишем команду:
git checkout main^
Результат:
- Пройдёмся несколько раз по дереву коммитов.
- Пропишем команды:
git checkout C3
git checkout HEAD^
git checkout HEAD^
git checkout HEAD^
Результат:
Дано:
-
Переместись на первого родителя ветки bugFix. Это отделит HEAD от ветки.
-
Нужно получить визулизацию, как на картинке.
- Пропишите команду:
git checkout HEAD^
Результат:
Переместиться на 4 коммита назад.
- Пропишите команды:
git checkout HEAD~4
Результат:
-
Одна из наиболее распространённых целей, для которых используются относительные ссылки - перемещение веток.
-
Можно напрямую прикрепить ветку к коммиту при помощи опции -f, n - количество шагов назад.
- Пропишем команду:
git branch -f main HEAD~n
- Переместит (принудительно) ветку main на n родителей назад от HEAD.
Переместить ветку main на 3 родителя назад.
- Пропишем команду:
git branch -f main HEAD~3
Результат:
- Относительная ссылка дала нам возможность просто сослаться на C1, а branch forcing (-f) позволил быстро переместить указатель ветки на этот коммит.
Дано:
Передвинь HEAD, main и bugFix так, как показано на визуализации.
- Пропишем команды:
git branch -f main C6
git branch -f bugFix HEAD~2
git checkout HEAD~1
Результат:
-
Есть много путей для отмены изменений в Git.
-
На низком уровне = добавление в коммит отдельных файлов и наборов строк.
-
На высоком = как изменения реально отменяются.
2 способа:
git reset
git revert
-
Отменяет изменения, перенося ссылку на ветку назад, на более старый коммит.
-
Это своего рода "переписывание истории".
-
git reset перенесёт ветку назад, как будто некоторых коммитов вовсе и не было.
Посмотрим, как это работает.
- Пропишем команду:
git reset HEAD~1
Результат:
- Git просто перенёс ссылку на main обратно на коммит C1. Теперь наш локальный репозиторий в состоянии, как будто C2 никогда не существовал.
-
Reset отлично работает на локальных ветках, в локальных репозиториях. Но этот метод переписывания истории не сработает на удалённых ветках, которые используют другие пользователи.
-
Чтобы отменить изменения и поделиться отменёнными изменениями с остальными = git revert.
Посмотрим, как это работает.
- Пропишем команду:
git revert HEAD
Результат:
- появился новый коммит. Дело в том, что новый коммит C2' просто содержит изменения, полностью противоположные тем, что сделаны в коммите C2. После revert можно сделать push и поделиться изменениями с остальными.
Дано:
-
Отмени самый последний коммит на ветках local и pushed. Всего будет отменено два коммита (по одному на ветку).
-
Помни, что pushed - это remote ветка, а local - это локальная ветка.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git reset local~1
git checkout pushed
git revert pushed
Результат:
Итак, мы уже освоили основы Git:
- коммиты
- ветки
- перемещение по дереву изменений.
Теперь речь пойдёт о перемещении изменений — возможности, позволяющей разработчику сказать "Хочу, чтобы эти изменения были вот тут, а вот эти — вон там" и получить точные, правильные результаты, не теряя при этом гибкости разработки.
- Cherry pick = копировать несколько коммитов на место, где сейчас находишься (HEAD).
Делается с помощью команды:
git cherry-pick <Commit1> <Commit2> <...>
-
Вот репозиторий, где есть некие изменения в ветке side, которые мы хотим применить и в ветку main.
-
Мы можем сделать это при помощи команды rebase, которую мы уже прошли, но давай посмотрим, как cherry-pick справится с этой задачей.
- Пропишем команду:
git cherry-pick C2 C4
Результат:
- Копии С2 и С4 теперь в ветке, где был HEAD.
Дано:
- Cкопируй изменения из этих трёх веток в мастер. Чтобы понять, какие коммиты копировать, посмотри на визуализацию уровня.
- Пропишем команду:
git cherry-pick C3 C4 C7
Результат:
-
Git cherry-pick прекрасен = когда точно известно, какие коммиты нужны = известны их точные хеши.
-
Когда нет, то интерактивный rebase = опция -i.
-
Git откроет интерфейс просмотра того, какие коммиты готовы к копированию на цель rebase (target).
-
Показываются хеши коммитов и комментарии к ним, так что можно легко понять что к чему.
Для "реального" Git, этот интерфейс означает просто открытие файла в редакторе типа vim. Редактор заменит диалоговое окно (делают одно и тоже).
После открытия окна интерактивного rebase есть три варианта для каждого коммита:
-
Сменить положение коммита по порядку, переставив строчку с ним в редакторе.
-
"Выкинуть" коммит из ребейза. Для этого есть pick - переключение его означает, что нужно выкинуть коммит.
-
Соединить коммиты.
-
Появится окно интерактивного rebase.
-
Переставляем несколько коммитов (или удали кое-какие) и посмотри, что получится в итоге.
- Пропишем команду:
git rebase -i HEAD~4
Результат:
- Скопировали коммиты, как сами и указали.
Дано:
- Переставь коммиты при помощи интерактивного rebase в таком порядке, как указано на визуализации.
- Пропишем команду:
git rebase -i HEAD~4
Результат:
- Удаляем С2 через фиолетовую кнопку, переставляем С5 и С4 и нажимаем "Подтвержить".
Вот ситуация, которая часто случается при разработке: мы пытаемся отследить ошибку, но она не очень очевидна.
Для того, чтобы достичь успеха на этом поприще, мы используем несколько команд для отладки и вывода.
Каждая отладочная команда (команды) вывода находится в своём коммите.
В итоге мы нашли ошибку, исправили её и порадовались!
Но проблема в том, что мы хотим добавить в main только исправление ошибки из ветки bugFix. Если мы воспользуемся простым fast-forward, то в main попадут также отладочные команды. Должен быть другой способ...
Дано:
-
Git копировать только один из коммитов.
-
Убедись, что в ветку main попал коммит, на который ссылается bugFix.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git rebase -i HEAD~3
git branch -f main HEAD (или bugFix или С4)
git checkout main
git cherry-pick C4 (или bugFix)
Результат:
Есть некоторые изменения "newImage" и другие изменения "caption", которые связаны так, что находятся друг поверх друга в репозитории.
Штука в том, что иногда нужно внести небольшие изменения в более ранний коммит. В таком случае надо немного поменять newImage, несмотря на то, что коммит уже в прошлом.
Дано:
-
Переставить коммит так, чтобы нужный находился наверху при помощи = git rebase -i.
-
Внести изменения при помощи = git commit --amend = позволяет добавить новые изменения в последний коммит.
-
Переставить всё обратно при помощи = git rebase -i.
-
Переместить main на изменённую часть дерева, чтобы закончить уровень.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git rebase -i HEAD~2 (переставляем С3 и С2 местами при интерактивном окне) (после выполенния ветка caption вместе с HEAD указывает на C2')
git commit --amend (получим коммит С2'') (C2' и C2'' имеют одного родителя C3')
git rebase -i HEAD~2 (переставляем С3' и С2'' местами при интерактивном окне)
git branch -f main HEAD (перемещаем ветку main в HEAD)
Результат:
До этого использовали rebase -i для перестановки коммитов. Как только нужный нам коммит оказывался в конце, мы могли спокойно изменить его при помощи --amend и переставить обратно.
Такой подход может спровоцировать конфликты. Посмотрим, как справиться с этим cherry-pick.
- Cherry-pick = поместит любой коммит сразу после HEAD (только если этот коммит не является предком HEAD).
- Пропишем команду:
gir cherry-pick C2
Результат:
Дано:
- Та же самая задача. Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git checkout main
git cherry-pick C2
git branch -f main HEAD~1
git cherry-pick C2' C3
Результат:
Ветки просто двигать туда-сюда и они часто ссылаются на разные коммиты как на изменения данных в ветке.
- Ветки просто изменить, часто временны, постоянно меняют своё состояние.
В таком случае, где взять постоянную ссылку на момент в истории изменений? Для таких вещей, как релиз и большие слияния, нужно нечто более постоянное, чем ветка.
-
Git предоставляет нам теги.
-
Основная задача – ссылаться постоянно на конкретный коммит.
-
После создания никогда не сменят своего положения, так что можно с лёгкостью сделать checkout конкретного момента в истории изменений.
Создадим тег на C1, который будет нашей версией 1.
- Пропишем команду:
git tag v1 C1
Результат:
-
Тег v1 = ссылается на C1 явным образом.
-
Если конкретный коммит не указан, гит пометит тегом HEAD.
Дано:
- Создайте теги так, как показано на визуализации, и потом перейди на тег v1. Обрати внимание, что ты перейдёте в состояние detached HEAD, так как нельзя сделать коммит прямо в тег v1.
- Пропишем команды:
git checkout C2
git tag v1 C2
git tag v0 C1
Результат:
Теги являются прекрасными ориентирами в истории изменений, поэтому в git есть команда, которая показывает, как далеко текущее состояние от ближайшего тега. И эта команда называется git describe
Git describe помогает сориентироваться после отката на много коммитов по истории изменений. Такое может случиться, когда вы сделали git bisect или если вы недавно вернулись из отпуска.
git describe <ref>
- ref — это что-либо, что указывает на конкретный коммит. Если не указать ref, то git будет считать, что указано текущее положение (HEAD).
Вывод команды выглядит примерно так:
<tag>-<numCommits>-g<hash>
-
tag – ближайший тег в истории изменений.
-
numCommits – на сколько далеко мы от этого тега.
-
hash – хеш коммита, который описывается.
- Пропишем команду:
git tag v2 C3
Результат:
-
git describe main выведет:
- v1-2-gC2
-
git describe side выведет:
- v2-1-gC4
Дано:
- Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git commit
git describe main
git describe bugFix
Результат:
Дано:
-
У нас тут куча веток! Было бы круто перенести все изменения из них в мастер.
-
Но начальство усложняет нашу задачу тем, что желает видеть все коммиты по порядку. Так что коммит С7' должен идти после коммита С6' и так далее.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git checkout bugFix
git rebase main
git checkout side
git rebase -i bugFix
git checkout another
git rebase -i side
git branch -f main HEAD
Результат:
- Так же как тильда (~), каретка (^) принимает номер после себя.
Но в отличие от количества коммитов, на которые нужно откатиться назад (как делает ~).
-
Номер после ^ определяет, на какого из родителей мерджа надо перейти. Учитывая, что мерджевый коммит имеет двух родителей, просто указать ^ нельзя.
-
Git по умолчанию перейдёт на "первого" родителя коммита.
- Пропишем команду:
git checkout main^2
Результат:
- Пропишем команды:
git checkout HEAD~
git checkout HEAD^2
git checkout HEAD~2
git checkout HEAD~^2~2
Результат:
Дано:
-
Cоздай ветку в указанном месте.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команду:
git branch -f bugWork HEAD~^2^
Результат:
Дано:
-
У нас тут по несколько коммитов в ветках one, two и three. Не важно почему, но нам надо видоизменить эти три ветки при помощи более поздних коммитов из ветки main.
-
Ветка one нуждается в изменении порядка и удалении C5. two требует полного перемешивания, а three хочет получить только один коммит.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git checkout one
git cherry-pick C4 C3 C2
git checkout two
git cherry-pick C5 C4' C3' C2'
git branch -f three C2
Результат:
- Удалённые репозитории — копии репозитория, хранящиеся на другом компьютере. Можете связываться с этим компьютером через Интернет, что позволяет вам передавать коммиты туда и сюда.
Свойства:
-
средство резервного копирования. Локальные репозитории способны восстанавливать файлы, используя предыдущие состояния, но информация хранится локально. Потеряв все свои локальные данные, способны восстановить их при наличии копии своего репозитория на другом компьютере.
-
процесс разработки более социальным. Когда копия проекта размещена в другом месте, коллеги запросто могут внести свой вклад в ваш проект или забрать последние и актуальные изменения.
-
git clone в реальной жизни - создаст локальную копию удалённого репозитория (например, с GitHub).
-
В тренажёре - создаёт удалённый репозиторий на основе вашего локального репозитория. На самом деле, это является полной противоположностью реальной команды.
- Пропишем команду:
git clone
Результат:
- В локальном репозитории по предыдущему примеру появилась новая ветка с именем o/main = удалённой веткой.
Свойтва:
-
Отражают состояние удалённых репозиториев (с того момента, как обращались к этим удалённым репозиториям в последний раз).
-
Отслеживать разницу между вашими локальными наработками и тем, что было сделано другими участниками.
-
Извлечение их, отделяет = detaching HEAD. Git делает это потому, что не можете работать непосредственно в этих ветках; сперва вам необходимо сделать наработки где-либо, а уж затем делиться ими с удалёнными репозиториями (после чего удалённые ветки будут обновлены).
- o/ в названии ветки = обозначениe удалённых веток.
Формате:
<удалённый репозиторий>/<имя ветки>
-
Имя ветки o/main:
-
*main = имя ветки.
-
o = имя удалённого репозитория.
-
-
В работе не как o, а как origin.
- Извлечём (check out) удалённую ветку и посмотрим что произойдёт.
- Пропишем команды:
git checkout o/main
git commit
Результат:
- Отделил detached HEAD и не обновил o/main, когда мы добавили новый коммит. Всё потому, что o/main обновится тогда и только тогда, когда обновится сам удалённый репозиторий.
Дано:
-
Выполните коммит единожды на main, а затем на o/main (предварительно переключившись на эту ветку). Это наглядно продемонстрирует поведение удалённых веток, а также покажет, как изменения влияют на состояние удалённых репозиториев.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git commit
git checkout o/main
git commit
Результат:
Работа с удалёнными git репозиториями сводится к передаче данных "в и из" других репозиториев.
Отправляя коммиты туда-обратно, можем делиться любыми изменениями, которые отслеживает git (следовательно, делиться новыми файлами, свежими идеями, любовными письмами и т.д.).
-
Извлечение данных из удалённого репозитория = git fetch.
-
Как только изменим представление удалённого репозитория, удалённые ветки обновятся соответствующим образом и отобразят это представление.
Имеется удалённый репозиторий, который содержит в себе два коммита, отсутствующих в нашем локальном репозитории.
- Пропишем команду:
git fetch
Результат:
-
Коммиты C2 и C3 были успешно скачаны в локальный репозиторий.
-
Удалённая ветка o/main отобразила эти изменения соответствующим образом.
git fetch выполняет две операции:
-
Cвязывается с указанным удалённым репозиторием и забирает все те данные проекта, которых у вас ещё нет, при этом...
-
Должны появиться ссылки на все ветки из этого удалённого репозитория (например, o/main).
Фактически, git fetch синхронизирует локальное представление удалённых репозиториев с тем, что является актуальным на текущий момент времени.
- git fetch 'общается' с удалёнными репозиториями посредством Интернета (через такие протоколы, как http:// или git://).
- git fetch забирает данные в ваш локальный репозиторий, но не сливает их с какими-либо вашими наработками и не модифицирует то, над чем вы работаете в данный момент.
Важно это помнить и понимать, потому что многие разработчики думают, что, запустив команду git fetch, они приведут всю свою локальную работу к такому же виду, как и на удалённом репозитории. Команда всего лишь скачивает все необходимые данные, но вам потребуется вручную слить эти данные с вашими, когда вы будете готовы.
- git fetch = процедура скачивания новые данные из удаленного репозитория, но не сливает их с вашими локальными файлами.
Дано:
-
Запустите git fetch и скачайте все коммиты!
-
Нужно получить визулизацию, как на картинке.
- Пропишем команду:
git fetch
Результат:
git pull состоит из двух команд:
-
git fetch = загрузка изменений из удаленного репозитория. Cкачивает новые данные из удаленного репозитория, но не сливает их с вашими локальными файлами.
-
git merge = слияние загруженных изменений с локальной веткой. Объединяет новые изменения из удаленной ветки с текущей локальной веткой.
-
Коммит слияния: Если слияние происходит успешно, git автоматически создаст новый "коммит слияния" для фиксации этих изменений. Этот коммит будет иметь два родительских элемента: родительский коммит из вашей локальной ветки и коммит из ветки, которую вы скачали.
- Пропишем команды:
git fetch
git merge
git pull
Результат:
-
Cкачали C3 с помощью команды fetch и затем объединяем эти наработки с помощью git merge o/main.
-
Ветка main отображает изменения с удалённого репозитория (в данном случае — с репозитория origin).
-
git pull существенно уменьшает работу, если бы использовали git fetch и слияние (merging) скачанной ветки.
Дано:
- Нужно получить визулизацию, как на картинке.
- Пропишем команду:
git pull
Результат:
Cкачивать наработки и изменения, которые были сделаны в удалённом репозитории.
Это означает, что нам следует "сделать вид", как будто знаем о том, что удалённый репозиторий, с которым работаем, был изменён одним из ваших коллег / друзей / единомышленников. Это может быть какая-то ветка, либо же какой-то конкретный коммит.
- Поведение команды fakeTeamwork по умолчанию заключается в том, чтобы просто "инициировать" коммит на main.
- Пропишем команду:
git fakeTeamwork
Результат:
- Удалённый репозиторий был изменён при помощи добавления нового коммита.
- Пропишем команду:
git fakeTeamwork foo 3
Результат:
- Добавление трёх коммитов в ветку foo на удалённом репозитории.
Дано:
-
Склонируйте удалённый репозиторий (с помощью git clone), симулируйте любые изменения на этом удалённом репозитории, сделайте какие-нибудь коммиты и затем скачайте "чужие" изменения.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git clone
git fakeTeamwork main 2
git commit
git pull
Результат:
Cкачали изменения с удалённого репозитория и включили их в локальные наработки.
Способ противоположным тому, которым пользовались ранее для скачивания наработок = git pull. Cпособ - использование команды git push.
-
git push отвечает:
-
за загрузку ваших изменений в указанный удалённый репозиторий.
-
включение локальных коммитов в состав удалённого репозитория.
-
-
git push = "публикацию" своей работы.
Поведение команды git push без аргументов варьируется в зависимости от значения push.default, указанной в настройках git. Значение по умолчанию зависит от версии git, которую вы используете. Здесь используется значение = upstream.
- У нас имеются изменения, которых нет в удалённом репозитории. Давайте же закачаем их туда!
- Пропишем команду:
git push
Результат:
-
Удалённый репозиторий получил новый коммит C2.
-
Ветка main на удалённом репозитории теперь указывает на C2.
-
Локальное отображение удалённого репозитория (o/main) изменилось соответственно. Всё синхронизировалось.
Дано:
-
Просто поделитесь своими двумя новыми коммитами с удалённым репозиторием
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git commit
git commit
git push
Результат:
-
Как забирать = pull = чужие коммиты.
-
Как закачивать = push = свои наработки и изменения.
Нюансы возникают тогда, когда история репозитория расходится.
-
Допустим, склонировали репозиторий в понедельник и начали разрабатывать какую-то новую и уникальную часть приложения.
-
В пятницу вечером готовы опубликовать фичу.
-
Но, о нет! Ваш коллега в течение недели написал кучу кода, который делает все наработки устарелыми. Код был также закоммичен и опубликован на удалённом репозитории, поэтому код базируется на устаревшей версии проекта и более не уместен.
-
В этом случае использование команды git push является сомнительным.
-
Как поведёт себя команда git push, если вы её выполните?
-
Может быть, она изменит удалённый репозиторий и вернёт всё к тому состоянию, которое было в понедельник?
-
Может, команда попробует добавить ваш код, не удаляя при этом новый? Или же она проигнорирует ваши изменения, так как они уже устарели?
-
-
По причине того, что в данной ситуации (когда история расходится) слишком много двусмысленностей и неопределённостей, git не даст закачать (push) ваши изменения.
- Принуждать включить в состав своей работы все те последние наработки и изменения, которые находятся на удалённом репозитории.
- Пропишем команду:
git push
Результат:
-
Ничего не произошло.
-
Всё потому, что команда git push не выполнилась успешно.
-
Дело в том, что ваш последний коммит C3 основан на удалённом коммите C1. В свою очередь, удалённый репозиторий уже изменился под воздействием C2. Вот почему git отклонил ваш push.
- Перебазировать свою работу на самую последнюю версию удалённой ветки.
Существует множество способов сделать это, но наиболее простой способ 'сдвинуть' свои наработки - через перебазировку или rebasing.
- Пропишем команды:
git fetch
git rebase o/main
git push
Результат:
-
Обновили локальный образ удалённого репозитория средствами git fetch.
-
Перебазировали наработки, чтобы отражали все изменения с удалённого репозитория.
-
Опубликовали их с помощью git push.
-
git merge не передвигает ваши наработки = создаёт новый коммит, в котором Ваши и удалённые изменения объединены.
-
Такой способ помогает указать git на то, что собираетесь включить в состав ваших наработок все изменения с удалённого репозитория = ваш коммит отразится на всех коммитах удалённой ветки, поскольку удалённая ветка является предком вашей собственной локальной ветки.
Если мы объединим (merge) вместо перебазирования (rebase)..
- Пропишем команды:
git fetch
git merge o/main
git push
Результат:
-
Обновили локальное представление удалённого репозитория с помощью git fetch.
-
Объединили ваши новые наработки с нашими наработками (чтобы отразить изменения в удалённом репозитории).
-
Затем опубликовали их с помощью git push.
- git pull --rebase = аналог для совместно вызванных fetch и rebase.
- Пропишем команды:
git pull --rebase
git push
Результат:
- Тот же результат, как и ранее, но намного короче вызов команд.
- Пропишем команды:
git pull
git push
Результат:
- И снова - результат такой же, как и ранее.
Дано:
-
Рабочий процесс получения изменений (fetching), перебазирования/объединения (rebase/merging) и публикации изменений (pushing):
-
Склонируйте репозиторий.
-
Сфабрикуйте командную работу (1 коммит).
-
Сделайте свой собственный коммит (1 коммит).
-
Опубликуйте свои наработки посредством перебазировки (rebasing).
-
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git clone
git fakeTeamwork main 1
git commit
git pull --rebase
git push
Результат:
Когда работаете в составе большой команды разработчиков над проектом, то, вероятнее всего, ветвь main будет заблокирована.
Для внесения изменений в неё в git существует понятие запроса на слияние Pull Request. В такой ситуации если закоммитите свои наработки непосредственно в main ветвь, а после выполните git push, то будет сгенерировано сообщение об ошибке:
! [remote rejected] main -> main (TF402455: Pushes to this branch are not permitted; you must use a pull request to update this branch.)
! [удалённо отклонено] main -> main (TF402455: Изменение этой ветви запрещены; вы можете использовать pull request для обновления этой ветви.)
Удалённый репозиторий отклонил загруженные коммиты непосредственно в main ветку потому, что на main настроена политика, которая требует использование Pull request вместо обычного git push:
- Политика подразумевает процесс создания новой ветви разработки, внесение в неё всех необходимых коммитов, загрузка изменений в удалённый репозиторий и открытие нового Pull request.
Дано:
-
Создайте ещё одну ветвь под названием feature.
-
Отправьте изменения на удалённый репозиторий.
-
Не забудьте вернуть локальную main ветвь в исходное состояние (чтобы она была синхронизирована с удалённой). В противном случае у вас могут возникнуть проблемы при следующем выполнении git pull.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git branch -f main o/main
git checkout -b feature C2
git push
Результат:
Среди разработчиков, вовлечённых в большой проект, довольно распространённ приём — выполнять всю свою работу в так называемых фича-бранчах (вне main). А затем, как только работа выполнена, разработчик интегрирует всё, что было им сделано.
- Ряд разработчиков делают push и pull лишь на локальную ветку main = таким образом ветка main всегда синхронизирована с тем, что находится на удалённом репозитории (o/main).
Для этого рабочего процесса совместили две вещи:
-
Интеграция фича-бранчей в main.
-
Закачку (push) и скачку (pull) с удалённого репозитория.
Быстренько вспомним, как нам обновить main и закачать выполненную работу.
- Пропишем команды:
git pull --rebase
git push
Результат:
-
Перебазировали нашу работу на новенький коммит, пришедший с удалённого репозитория.
-
Закачали свои наработки в удалённый репозиторий.
Дано:
-
Есть три фича-бранчи (фича-ветки) - side1 side2 и side3.
-
Нам необходимо закачать каждую из них по очереди на удалённый репозиторий.
-
При этом удалённый репозиторий хранит в себе какие-то наработки, которые также следует скачать к себе.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git fetch (main останется на месте, если и использовали pull, то сметилась бы вместе с o/main)
git rebase o/main side1 (side1, указывающая на коммит С2 = перебазирется на ветку o/main и добавит копию С2 к этому коммиту, side1 укажет на С2')
git rebase side1 side2
git rebase side2 side3
git rebase side3 main
git push
Результат:
- Чтобы закачать (push) новые изменения в удалённый репозиторий, всё, что вам нужно сделать = это смешать последние изменения из удалённого репозитория.
Можем выполнить rebase или merge на удалённом репозитории (например, o/main).
За:
- Rebasing делает дерево коммитов более чистым и читабельным, потому что всё представляется единой прямой линией.
Против:
-
Метод rebasing явно изменяет историю коммитов в дереве. Пример:
-
Коммит C1 может быть перебазирован после C3. Соответственно, в дереве работа над C1' будет отображаться как идущая после C3, хотя на самом деле она была выполнена раньше.
Некоторые разработчики любят сохранять историю и предпочитают слияние (merging). Другие предпочитают иметь чистое дерево коммитов, и пользуются перебазировкой (rebasing). Всё зависит от ваших предпочтений и вкусов.
Дано:
-
Решите предыдущие задачи, но с помощью слияния (merging). Может быть, получится слегка неказисто, однако такое упражнение хорошо отразит суть различий.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git checkout main
git pull
git merge side1
git merge side2
git merge side3
git push
Результат:
- git знает, что ветка main соответствует o/main. Ветки имеют схожие имена и связь между локальной и удалённой ветками main выглядит вполне логично, однако, связь наглядно продемонстрирована в двух сценариях:
-
Во время операции pull коммиты скачиваются в ветку o/main и затем соединяются в ветку main. Подразумеваемая цель слияния определяется исходя из этой связи.
-
Во время операции push наработки из ветки main закачиваются на удалённую ветку main (которая в локальном представлении выглядит как o/main). Пункт назначения операции push определяется исходя из связи между main и o/main.
Связь между main и o/main объясняется не иначе как свойство "удалённое отслеживание" веток:
-
Ветка main настроена так, чтобы следить за o/main -- это подразумевает наличие источника для merge и пункта назначения для push в контексте ветки main. Когда вы клонируете репозиторий, это слежение включается автоматически.
-
В процессе клонирования git локально создаёт удалённые ветки для каждой ветки с удалённого репозитория (такие как o/main).
-
Затем git создаёт локальные ветки, которые отслеживают текущую, активную ветку на удалённом репозитории. В большинстве случаев - это main.
К тому моменту как git clone завершит своё выполнение = будет лишь одна локальная ветка, но можете увидеть все удалённые ветки.
Именно это объясняет, почему сразу после клонирования вы видите в консоли надпись:
local branch "main" set to track remote branch "o/main" (локальная ветка "main" теперь следит за удалённой веткой "o/main")
Можете сказать любой из веток, чтобы отслеживала o/main, и если так сделать, ветка будет иметь такой же пункт назначения для push и merge как и локальная ветка main.
- Значит, что можете выполнить git push, находясь на ветке totallyNotMain, и наработки с ветки totallyNotMain будут закачены на ветку main удалённого репозитория!
Выполнить checkout для новой ветки, указав удалённую ветку в качестве ссылки. Для этого необходимо выполнить команду:
git checkout -b totallyNotMain o/main
- Cоздаст новую ветку с именем totallyNotMain и укажет ей следить за o/main.
Выполним checkout для новой ветки foo и укажем ей, чтобы она отслеживала main с удалённого репозитория.
- Пропишем команды:
git checkout -b foo o/main
git pull
Результат:
- использовали o/main, чтобы обновить ветку foo.
- Пропишем команды:
git checkout -b foo o/main
git commit
git push
Результат:
- Закачали наработки на ветку main удалённого репозитория. Докальная ветка называется абсолютно по-другому.
Указать ветке отслеживать удалённую ветку — это просто использовать команду git branch -u. Выполнив команду:
git branch -u o/main foo
- Укажете ветке foo следить за o/main.
Если вы ещё при этом находитесь на ветке foo, то её можно не указывать:
git branch -u o/main
- Пропишем команды:
git branch -u o/main foo
git commit
git push
Результат:
Дано:
-
Выполним push наших наработок в ветку main на удалённом репозитории, при этом не скачивая и не создавая ветку main локально. Вместо этого вам следует создать ветку с именем side.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git checkout -b side o/main
git commit
git pull --rebase
git push
Результат:
Команда:
git push <удалённый_репозиторий> <целевая_ветка>
Выполняем такую команду:
git push origin main
Дословный перевод с английского будет таким:
- Перейди в ветку с именем "main" в локальном репозитории, возьми все коммиты и затем перейди на ветку "main" на удалённом репозитории "origin". На удалённую ветку скопируй все отсутствующие коммиты, которые есть у меня, и скажи, когда закончишь.
Указывая main в качестве аргумента "целевая_ветка", тем самым говорим git:
-
откуда будут приходить и куда будут уходить наши коммиты.
-
Аргумент "целевая_ветка" или "местонахождение" = синхронизация между двумя репозиториями.
- Указаны оба этих аргумента. Обратите внимание на местоположение, в котором мы находимся после чекаута.
- Пропишем команды:
git checkout С0
git push origin main
Результат:
- Обновили main на удалённом репозитории, принудительно указав аргументы в push.
- Тоже самое.
- Пропишем команды:
git checkout С0
git push
Результат:
- Команда -, так как HEAD потерялся и не находится на удалённо-отслеживаемой ветке.
Дано:
-
Обновим обе ветки foo и main на удалённом репозитории.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git push origin foo
git push origin main
Результат:
-
Источник и получатель коммитов были различными?
-
Что, еслизапушить коммиты из локальной ветки foo в ветку bar на удалённом репозитории?
Разделить источник и получатель аргумента <пункт назначения>, соедините их вместе, используя двоеточие:
git push origin <источник>:<получатель>
-
Обычно это называется refspec.
-
Refspec — это всего лишь модное имя для определения местоположения, которое git может распознать (например, ветка foo или просто HEAD~1).
- Источник = местоположение, которое git должен понять.
- Пропишем команду:
git push origin foo^:main
Результат:
- git видит в foo^ не что иное, как местоположение, закачивает все коммиты, которые не присутствуют на удалённом репозитории, и затем обновляет получателя.
А что если пункт назначения, в который запушить, не существует? Без проблем! Укажите имя ветки, и git сам создаст ветку на удалённом репозитории для вас.
- Пропишем команду:
git push origin main:newBranch
Результат:
Дано:
- Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git push origin foo^:foo
git push origin foo:main
git push origin main^:foo
git push origin main^:foo
git push origin foo:main
Результат:
Если указываете пункт назначения в команде git fetch, например так, как в следующем примере:
git fetch origin foo
- Git отправится в ветку foo на удалённом репозитории, соберёт с собой все коммиты, которые не присутствуют локально, и затем поместит их в локальную ветку под названием o/foo.
- Пропишем команду:
git fetch origin foo
Результат:
- Cкачали только коммиты с ветки foo и поместили их в o/foo.
Думали о параметре <пункт назначения>, как о месте, ветке, которая существует в обоих - локальном и удалённом репозитории. Верно?
На самом деле:
В данном случае git делает исключение, потому что вы, возможно, работаете над веткой foo, которую не хотите привести в беспорядок. Об этом упоминалось в ранних уроках по git fetch - эта команда не обновляет локальные 'не удалённые', она лишь скачивает коммиты (соответственно, можете инспектировать / объединять их позже).
"Что же тогда произойдёт, если я явно укажу оба параметра: и источник и получатель, пользуясь синтаксисом <источник>:<получатель> ?"
Если уверены в том, что закачать коммиты прямиком в вашу локальную ветку, тогда да, можете явно указать источник и получатель через двоеточние = таким приёмом лишь для ветки, на которой не находитесь в настоящий момент checkout. Теперь:
-
<источник> - это место на удалённом репозитории.
-
<получатель> - место в локальном репозитории, в который следует помещать коммиты.
Как уже было сказано, разработчики редко используют такой подход на практике.
- Пропишем команду:
git fetch origin C2:bar
Результат:
- git распознал C2 как место в origin и затем скачал эти коммиты в bar, которая является локальной веткой.
Поведение будет такое же, как и у git push.
Если команда git fetch выполняется без аргументов, она скачивает все-все коммиты с удалённого репозитория и помещает их в соответствующие удалённо-локальные ветки в локальном репозитории...
- Пропишем команду:
git fetch
Результат:
Дано:
-
Cкачайте лишь определённые коммиты так, как представлено в визуализации цели. Cледует явно указывать источник и получателя для обеих команд fetch.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git fetch origin C3:foo
git fetch origin C6:main
git checkout foo
git merge main
Результат:
Git использует параметр <источник> странным образом. Странность заключается в том, что Вы можете оставить пустым параметр <источник> для команд git push и git fetch:
git push origin :side
git fetch origin :bugFix
Что же будет с веткой, на которую мы делаем git push с пустым аргументом <источник>?
Она будет удалена.
- Пропишем команду:
git push origin :foo
Результат:
- Удалили ветку foo в удаленном репозитории, попытавшить протолкнуть(git push) в неё "ничего".
- Если попытаемся притянуть изменения (git fetch) из "ничего" к нам в локальный репозиторий, то это создаст новую ветку:
- Пропишем команду:
git fetch origin :bar
Результат:
- Появилась локальная ветка bar.
Дано:
-
Удалить одну ветку в удаленном репозитории и создать новую ветку в локальном
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git push origin :foo (удалит)
git fetch origin :bar (добавит)
Результат:
-
git pull:
-
Cначала выполняет git fetch
-
Cледом сразу git merge с той веткой, в которую притянулись обновления командой fetch.
-
Другими словами, это все равно, что выполнить git fetch с теми же аргументами, которые вы указали для pull, а затем выполнить git merge с веткой, указанной в аргументе <приемник> команды pull.
Эквивалентны:
git pull origin foo
git fetch origin foo
git merge o/foo
Эквивалентны:
git pull origin bar:bugFix
git fetch origin bar:BugFix
git merge bugFix
Cначала выполнится fetch с аргументом указанным к pull, а merge выполняется с теми изменениями, которые будут скачаны командой fetch.
- Пропишем команду:
git pull origin main
Результат:
- Указали main, поэтому как обычно все обновления притянулись на ветку o/main. Затем мы слили (merge) обновленную ветку o/main с веткой, на которой мы находимся.
Будет ли это работать, если указать <источник> и <приемник>? Проверим:
- Пропишем команду:
git pull origin main:foo
Результат:
- Cоздали новую ветку foo в локальном репозитории, скачали на неё изменения с ветки main удаленного репозитория, а затем слили ветку с веткой bar, на которой находились.
Дано:
-
Cкачать несколько изменений, создать несколько новых веток, слить одни ветки в другие.
-
Нужно получить визулизацию, как на картинке.
- Пропишем команды:
git pull origin C3:foo
git pull origin C2:main
Результат: