GIT для продвинутых – проще некуда. Часть 2

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

Полочки stash

Допустим, что вы работаете локально и изменяете какие-то файлы и хотите слить текущую ветку с мастером.

git fetch
git merge origin/master

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

Такое же может произойти, если вы изменили файл и переключаетесь на другую ветку. Такое вполне легально – менять файлы и в процессе работы переключаться между ветками. Прямо сейчас мы можем выполнить git checkout и переключиться на другоую ветку или можем создать новую.

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

git stash

В результате мы должны увидеть в консоли:

Saved working directory and index state WIP on testchanges: a168c47 launch complete 20201203

Если после этого выполнить команду git status, то вы увидите, что все локальные изменения исчезли. Но они не ушли бесследно, они все еще существуют, просто во временном хранилище. Можно сказать, что вы отложили их на полку.

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

git stash list

У меня только одно отложенное на полку изменение, поэтому в результате я вижу только одну строку:

stash@{0}: WIP on testchanges: a168c47 launch complete 20201203

Здесь такие же ID, которые я видел при отложении изменений.

Теперь мы можем сливать удаленные изменения и переключаться на другую ветку. Когда все закончено, мы просто должны взять изменения с полки и снова наложить их, выполнив команду:

git stash pop

Смотрим на статус и теперь мы снова должны видеть изменения. Если же выполнить git stash list, то вы не увидите ничего, потому что операция pop не просто накладывает отложенные изменения, но и удаляет из стека точку.

Не все следует добавлять в репозиторий

Далеко не все файлы должны попадать в git. Например, я не очень люблю хранить web.config файлы в репозитории, потому что там могут быть какие-то важные данные или банально у каждого программиста могут быть свои настройки. У нас сейчас на работе web.config файл находится в git и поэтому многие вынуждены менять его и после этого должны следить, что никто не закоммитит изменения.

Не стоит коммитить такие файлы как DLL, EXE, OBJ и многие другие. Любые файлы, которые генерируются или являются результатом компиляции не стоит помещать в систему контроля версий.

Но если игнорировать подобные файлы, то они будут бесить при выполнении команды git status и в определенных проектах количество файлов может быть на столько большим, что git status будет отображать слишком много информации.

.gitignore

Создайте пару файлов для примера с расширением exe и obj:

echo >> hello.exe echo >> hello.obj

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

Давайте создадим файл .gitignore, я это сделаю с помощью vi, а вы можете использовать любой текстовый редактор:

vi .gitignore

В этот файл добавляем две строки:

*.exe
*.obj

Сохраняем файл и проверяем git status – файлы больше не видны, они игнорируются. Если вы работаете над .NET проектом, то вполне логично было бы добавить в .gitignore еще и эти строки:

bin/*
obj/*

Когда я работал над CMS, то у меня игнорировалась и папка, в которой хранятся картинки:

Images/*

Таким образом, когда я тестировал CMS и загружал какие-то тестовые картинки, то они не появлялись. Но когда я загружал файл через CMS и реально хотел его закинуть в GIT и толкнуть потом на продакшн сервер, то его все же нужно было добавить в git и как это сделать? git add закончится ошибкой, потому что нельзя нарушать правила .gitignore, которые мы сами же установили. Но можно заставить их нарушить указав ключ -f:

git add -f hello.exe

Эта команда позволяет нам нарушить правило.

Фильтруем нужные ветки

Давайте переключимся на файл config локального репозитория, о котором я уже говорил раз и намекал, что в нем есть интересная настройка. Из текущей папки репозитория выполняем команду:

vi ./.git/config

В разделе [remote "origin"] должен быть URL к репозиторию с кодом и этот URL вы можете поменять, если перенесете хранилище кода с одного сервера на другой. И еще одна строка, которая задает fetch параметр:

[remote "origin"]
        url = https://github.com/mflenov/demo.git
        fetch = +refs/heads/*:refs/remotes/origin/*

И параметр fetch очень даже полезный, особенно, если вы работаете над проектом, над которым работает большое количество команд. Каждая команда может создавать ветки, которые начинаются с имени команды. Например, я работаю в команде performance, и мы должны создавать ветки в формате performance/xxxxxx.

У нас в компании много различных команд и у каждой свой префикс. Каждый раз, когда я выполняю git fetch сервер будет возвращать мне ветки абсолютно всех команд. А оно мне надо? Конечно же нет, я никогда не будут работать с кодом других команд.

Удаляем строку:

fetch = +refs/heads/*:refs/remotes/origin/*

Она говорит, что при выполнении fetch система будет забирать все ветки remotes/origin/*. На то, что будут забираться все указывает звездочка.

Нам нужен мастер, так что добавляем эту строку:

fetch = +refs/heads/master:refs/remotes/origin/master

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

И нам нужны ветки наших коллег по команде, которые используют префикс performance, так что добавляем еще и эту строку:

fetch = +refs/heads/performance/*:refs/remotes/origin/performance/* 

Полностью раздел remote в файле config может выглядеть примерно так:

[remote "origin"]
        url = https://github.com/mflenov/demo.git
        fetch = +refs/heads/master:refs/remotes/origin/master
        fetch = +refs/heads/performance/*:refs/remotes/origin/performance/* 

Переместить / удалить

Ну и напоследок несколько простых команд, которые также могут пригодиться.

Переименование ветки – можно создать новую ветку с новым именем от текущей и работать с ней:

git checkout -b wip/renamed wip/testchanges

Рабочий вариант, но просто у вас будет две ветки - старая и новая. Если вам не нужна ветка wip/testchanges, то можно переименовать с помощью git branch -m:

git branch -m wip/renamed 

Если не ошибаюсь, то -m означает move. Мы как бы превращаем текущую ветку в wip/renamed.

Если теперь посмотреть на статус, то в первой же строке должно быть:

On branch wip/renamed

А если выполнить git branch, то в списке всех локальных веток вы не увидите wip/testchanges, эта ветка исчезла.

Удалить ненужную ветку можно выполнив команду git branch с ключом -D:

git branch -D secondone

Но если честно, я очень редко удаляю локальные ветки.

В современном мире при использовании пул запросов сразу после завершения запроса ветка удаляется с удаленного сервера. Если на сервере ветки нет, то некоторые не любят хранить ее и локально, поэтому могут вызывать команду:

git prune

Эта команда удаляет локальные ветки, которые указывают на удаленные ветки, которые больше не существуют.

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

Merge vs Rebase

Я работаю с git с 2009-го года и за все это время ни разу еще не делал rebase. Всегда только сливаю ветки и вам рекомендую. Я НЕ против Rebase, просто я не использую те функции, которые другие выделают как преимущества.

Когда делать rebase, а когда merge? Всегда делать merge. Если не есть причина не делать merge, то подумайте еще раз, может все таки сделать merge? Если вы еще раз подумали и поняли, что нужен rebase и другого выхода просто нет, то только в этом случае делаете rebase.

Как происходит merge мы уже знаем. Создается ветка, в ней вы коммитите изменения и если возникает ситуация, когда вы хотите в свою ветку принести изменения из мастера, которые делают другие программисты, вы просто вызываете

git merge origin/mster

Ваши коммиты остаются на месте, где они и были, в точке слияния изменения в мастере прилетают в вашу ветку:

Как работает rebase? Вы создали ветку и сделали три изменения. Теперь вы вызываете команду rebase и ваши три изменения отменяются. Не навсегда, а временно. Теперь как бы создается новый бренч на месте вашего существующего от текущей точки мастер и на текущую точку мастера накладываются ваши изменения. Они могут накладываться в таком же количестве в виде трех изменений, а можно все изменения объединить в один большой коммит.

На этом рисунке показан вариант, когда не создается новый коммит.

Сторонники rebase говорят, что мы очень нежные и не любим критику, поэтому если были какие-то баги в Изм1 или Изм2, но исправлены в Изм3 мы хотим спрятат баги. Во время rebase можно изменения 1, 2 и 3 объединить в один коммит и закоммитить как одно целое. Это зло, потому что вы теряете историю. Именно поэтому Линус поправил свое отношение к rebase. Хотите терять свою историю? Ваше право, но никогда не удаляйте историю изменений других программистов.

Я rebase ни разу не делал, но судя по документации он создает для каждого изменения новый коммит, а это новый SHA. Допустим, что изначально мы создали ветку fix1.

  • Ветка fix1 с коммитом #736berq
  • Создается ветка fix2 от ветки 1 и у нее тоже будет коммит с номером #736berq
  • Теперь мы делаем rebase на fix1 и для нашего коммита уже будет новый SHA.

Когда мы будем мерджить изменения из fix1 и fix2 в мастер, то одно и то же изменение git будет видеть под разными ID и будет считать, что они разные и это зло. Мы можем потерять историю – кто именно внес изменение и какое оно было.

Не прячьте историю. Старайтесь делать merge. Я без rebase живу уже 11 лет и не планирую его делать даже ради этого видео.

Выполнить rebase очень просто, выполняем команду

git rebase

Снимаем сливки

Даже не знаю, как лучше перевести в контексте системы управления кодом слово cherry-pick. Это можно привести, как брать лучшее. Когда мы делаем. Merge, то все изменения в ветке будут наложены на мастер. Cherry-pick позволяет забрать только определенный коммит. Если у вас есть в мастере определенный коммит, который вы хотите забрать себе, то можно выполнить команду:

git cherry-pick a168c47ac96dcfd9d96c6e69b2dc7e28404479b8

Подобный подход часто используется в команиях, где есть несколько версий продукта. Например, есть Програимма 2020 и есть более старая версия Программа 2019. Версия 2020 может быть мастером, а 2019 может быть какой-то веткой от мастера, которую создали много лет назад и до сих пор поддерживают.

Если найден баг, который касается обеих версий, вы можете создать ветку от Версии 2020, исправить баг, закомитить его и слить с мастером, то есть с 2020. Теперь коммит, который мы слили с мастером можно забрать с помощью cherry-pick и поместить в Программа 2019, потому что сливать нельзя. Если вы попытаетесь слить любую ветку, созданную от 2020 в ветку 2019, то абсолютно все изменения новой версии попадут в 2019. А cherry-pick заберет только изменения, которые нужны.

Итак, данная задача решается просто:

  • Создаем ветку от 2020
  • Исправляем изменения, коммитим их и запоминаем SHA
  • Сливаем фикс бага с 2020
  • Переключаемся на 2019 и делаем cherry-pick, чтобы только наш коммит прилетел из 2020 в 2019, а не абсолютно все изменения.



Внимание!!! Если ты копируешь эту статью себе на сайт, то оставляй ссылку непосредственно на эту страницу. Спасибо за понимание

Комментарии

Паника, что-то случилось!!! Ничего не найдено в комментариях. Срочно нужно что-то добавить, чтобы это место не оставалось пустым.

Добавить Комментарий

О блоге

Программист, автор нескольких книг серии глазами хакера и просто блогер. Интересуюсь безопасностью, хотя хакером себя не считаю

Обратная связь

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

Пишите мне