много рецептов: https://webhamster.ru/mytetrashare/index/mtb0/
Ветвление и слияние веток
Клонируем репозиторий:
git clone https://....
Создаем ветку:
git branch new-branch
и переключаемся на нее:
git checkout new-branch
или обе команды в одной (создаем и сразу переключаемся на ветку):
git checkout -b new-branch
Увидеть какие ветки есть можно так:
git branch -a
Слияние веток
Например - есть две ветки master и new-branch.
Когда говорят, что нужно слить ветку new-branch с веткой master, то это значит, что нужно выполнить переключение на ветку master:
git checkout master
и выполнить слияние ветки new-branch:
git merge new-branch
Разрешение конфликтов при слиянии
Конфликт возникает, если изменения в одном и том же файле были произведены в двух ветках и мы пытаемся слить эти ветки.
Для того, чтобы увидеть суть конфиликта нужно видеть три состояния:
- Состояние первой ветки
- Состояние второй ветки
- Общее начальное состояние веток до изменений.
Включить отображение различий и общего начального стсоояния можно так:
git config merge.conflictstyle diff3
Допустим, была ветка master, от которой ответвились две ветки - develop и feature, в которых были произведены изменения в одном и том же файле.
При попытке слияния ветки feature с веткой develop:
git checkout develop git merge feature
будет создан коммит, в котором зафиксирован конфликт двух веток:
Auto-merging main.go CONFLICT (content): Merge conflict in main.go Automatic merge failed; fix conflicts and then commit the result.
Увидеть суть конфликта можно в конце листинга указанного файла:
cat ./main.go ... <<<<<<< HEAD func sum (a, b int) int { return a + b ||||||| merged common ancestors func sum (a, b int) int { t := a + b return t ======= func sum(a, b int) int { t := a + b return t >>>>>>> feature }
Тут три фрагмента:
- Первый принадлежит ветке develop:
func sum (a, b int) int { return a + b }
- Второй - принадлежит общему предку - коммиту в ветке master:
func sum (a, b int) int { t := b + a return t }
- Третий - ветке feature
func sum(a, b int) int { t := a + b return t }
Теперь нужно как-то разрешить конфликт. Например, мы знаем, что нужно оставить самые последние версии (по времени внесения изменений).
Отлично написано тут: https://www.rosipov.com/blog/use-vimdiff-as-git-mergetool/
Для разрешения конфликта будем использовать дефолтную утилиту vimdiff.
Настраиваем git для использования vimdiff в качестве mergetool:
git config merge.tool vimdiff git config mergetool.prompt false
Приступаем к разрешению конфликта:
git mergetool
Листаем вниз и видим конфликт:
func sum (a, b int) int { | func sum (a, b int) int { | func sum(a, b int) int { return a + b | t := a + b | t := a + b ----------------------------------------| return t | return t ----------------------------------------| ----------------------------------------| ---------------------------------------- ----------------------------------------| ----------------------------------------| ---------------------------------------- ----------------------------------------| ----------------------------------------| ---------------------------------------- ----------------------------------------| ----------------------------------------| ---------------------------------------- ----------------------------------------| ----------------------------------------| ---------------------------------------- ----------------------------------------| ----------------------------------------| ---------------------------------------- ----------------------------------------| ----------------------------------------| ---------------------------------------- ./main_LOCAL_25754.go 44,2 97% ./main_BASE_25754.go 45,2 97% ./main_REMOTE_25754.go 45,2 97% <<<<<<< HEAD func sum (a, b int) int { return a + b ||||||| merged common ancestors func sum (a, b int) int { t := a + b return t ======= func sum(a, b int) int { t := a + b return t main.go 47,8 95%
Тут мы видим:
- слева - состояние ветки в которую мерджим изменения (обозначены LOCAL)
- в середине - общий предок (BASE)
- справа - состояние ветки которую мы мерджим (REMOTE)
- Внизу - конфликт
Нужно оставить изменения, которые содержатся в ветке REMOTE. Для этого переключаемся между окошками с помощью Ctrl + w,j на нижнее окошко, листам вниз до конфликта и жмем последовательно:
Esc :diffg RE :wqa
Эта последовательность команд переводит редактор в режим команд (кнопочка Esc), затем применяем изменения из ветки REMOTE, а затем сохраняем изменения.
Измнения из LOCAL или BASE применяются соответственно командами:
- diffg BA
- diffg LO
И наконец можно выполнить commit:
git commit -a -m "Branch 'feature' merged to 'develop'"
Разрешение всех конфликтов слияния в пользу текущего или входящего состояния
Если при merge возникли конфиликты, то можно одной командой разрешить все конфликты в ту или другую пользу.
Оставить все конфликтные участки в текущем состоянии:
git checkout --ours .
Применить во всех случаях конфликтов входящие изменения:
git checkout --theirs .
Переключиться на нужный коммит
Находим нужный коммит с помощью
git log
И переключаемся на него:
git checkout <commit_hash>
Тегирование коммитов
Тегируем текущий коммит и пушим тег:
git tag <tag_name> git push <remote_name> <branch_name> <tag_name>
Например:
git tag ops.07 git push origin master ops.07
Откат изменений
git reset --hard
Откат определенных коммитов (rebase & cherry-pick vs. revert): https://www.pixelstech.net/article/1549115148-git-reset-vs-git-revert
git revert ...
Объединение коммитов
https://git-scm.com/book/ru/v1/%D0%98%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D1%8B-Git-%D0%9F%D0%B5%D1%80%D0%B5%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D1%8C-%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8
Объединить коммиты можно с помощью interactive rebase:
git rebase -i HEAD~3
Откроется тектовый редактор, в котором в начале будут перечислены коммиты (в данном случае - 3 штуки, начиная с самого старого) и применяемые к ним действия (по умолчанию - pick). Для того, чтобы объединить коммиты нужно для самого первого (старого) оставить pick, а для остальных прописать squash (или сокращенно s). В результате, изменения в коммитах объединятся в один (самый старый).
Объединение всех коммитов ветки
git reset `echo "Total Squash Commit" | git commit-tree HEAD^{tree}`
Разбиение коммитов и распределение их по веткам
Предствьте себе, что случилась неприятность - вы недумая коммитили в master, в одном коммите могли оказаться изменения, касающиеся разных фич. И всё было хорошо, пока кто-то не захотел сделать ревью и увидел ХАОС. Что нужно - нужно разбить сделанные коммиты по веткам фич. Для этого - нужно разбить все старые коммиты и потом применить их к нужным веткам. Как разбить коммит, который зарыт глубоко в истории?? Поможет rebase! Что он делает - применяет коммиты последовательно к тому, на котором мы делаем rebase. Как вмешаться в этот процесс - запустить его в интерактивном режиме, когда мы можем указать что делать с каждым коммитом.
Допустим - у нас поверх последнего хорошего коммита выполнено уже 4 нехороших хаотичных.
Первое - выполняем интерактивный ребейс:
git rebase -i HEAD~4
Либо прямо делаем rebase, указав хеш коммита:
git rebase -i <LAST_GOOD_COMMIT>
Откроется окно редактора, в котором будет список коммитов, начиная с самого старого. На против каждого будет стоять pick. Если сейчас ничего не менять - то ничего и не произойдет. Все коммиты применятся и мы откажемся на последнем. Но! Если для первого поставить вместо pick - edit, сохраним файлик и выйдем, то мы окажемся на этом коммите. А дальше - магия git. \ Отменяем коммит, на котором мы находимся в данный момент (который был закоммичен последним с нашей точки зрения - а это тот, для которого мы указали edit):
git reset HEAD~
Проверим, что изменения откатились и увидим список измененных в этом коммите файлов:
git status
Например, файлы разбиты по каталогам feature1 и feature2 (будем считать так для простоты). Нам надо разбить изменения в этих каталогах на два коммита. Последовательно добавляем в stage каталоги (или файлы) и коммитим:
git add ./feature1/* git commit -m 'feature1 commit' git add ./feature2/* git commit -m 'feature2 commit'
В итоге наш крупный коммит был разбит на два. А теперь - продолжим rebase:
git rebase --continue
Всё.
Теперь эту процедуру можно повторить для остальных крупных беспорядочных коммитов.
А потом - ответвить от последнего хорошего коммита ветки и применять на эти ветки коммиты, соответствующие фичам:
git checkout <LAST_GOOD_COMMIT> git checkout -b feature1 git cherry-pick <FEATURE1_SHA1>
и для второй фичи:
git checkout <LAST_GOOD_COMMIT> git checkout -b feature2 git cherry-pick <FEATURE2_COMMIT_SHA1>
Скачать из репозитория git одну единственную папку
mkdir charts cd charts git init git remote add origin https://github.com/helm/charts.git git config core.sparseCheckout true echo "stable/jenkins" > .git/info/sparse-checkout git pull origin master
или
git archive --format tar --remote ssh://server.org/path/to/git HEAD docs/usage > /tmp/usage_docs.tar
Почистить репозиторий git
Допустим, у нас не было файлика .gitignore и в репозиторий попало много ненужного.
Теперь я хочу избавиться от ненужных файлов в репозитории.
Для этого:
- делаем коммит текущего состояния, вместе с файликом .gitignore
- удаляем все ненужные файлики из репы
- удаляем все файлики из индекса репозитория ( отдельный файлик можно удалить так: git rm –cached foo.txt )
git rm -r --cached .
- и добавляем все файлики обратно в индекс:
git add .
- Коммитим состояние
git commit -m ".gitignore fix"
No url found for submodule path
Иногда в проект попадают подпроекты, которые сами по себе являются репозиториями git. В этому случае они становятся submodules и git работает с ними как со ссылками на внешние репы. То есть он не сохраняет в репозитории файлы этих проектов, а скачивает их по требованию.
Однако, может так статься, что файлы подпроекта становятся частью проекта, а ссылки на внешние репозитории изменяются или перестают работать.
Если ссылка на внешний репозитрий изменилать, то следует поправить ее в файле .gitmodules в корне проекта:
[submodule "path_to_submodule"] path = path_to_submodule url = git://url-of-source/
А если подпроект нужно просто сделать частью проекта, то удаляем его так:
git rm --cached path_to_submodule
fatal: refusing to merge unrelated histories
Иногда, после манипуляций с историей коммитов нарушается связность историй веток и при попытке выполнения
git merge BRANCHNAME
Появляется сообщение вида:
fatal: refusing to merge unrelated histories
В этом случае - если нам нужно заменить содержимое одной ветки, содержимым другой, то в ветке куда делаем merge выполняем:
git pull origin BRANCHNAME -X theirs --allow-unrelated-histories
То есть - подтягиваем в текущую ветку все изменения из ветки репозитория BRANCHNAME, разрешаем нарушение истории коммитов (–allow-unrelated-histories) и автоматически разрешаем все конфликты в пользу применения входящих изменений (-X theirs)
Создать пустой коммит перед корневым
Удалить большие файлы из репозитория git
Найти большие файлы в текущей репе git:
git rev-list --objects --all | grep -f <(git verify-pack -v .git/objects/pack/*.idx| sort -k 3 -n | cut -f 1 -d " " | tail -10)
Удаляем те файлики, которые не нужны, используя пути к ним:
git filter-repo --path-glob '~~~FILE_PATH~~~' --invert-paths --force
Либо - можно удалять по расширению:
git filter-repo --path-glob '*.zip' --invert-paths --force
Теперь надо снова прописать remote
git remote add origin git@github.com:.........
И запушить всё:
git push --all --force git push --tags --force
Discussion