-
1. Введение
- 1.1 О системе контроля версий
- 1.2 Краткая история Git
- 1.3 Что такое Git?
- 1.4 Командная строка
- 1.5 Установка Git
- 1.6 Первоначальная настройка Git
- 1.7 Как получить помощь?
- 1.8 Заключение
-
2. Основы Git
-
3. Ветвление в Git
- 3.1 О ветвлении в двух словах
- 3.2 Основы ветвления и слияния
- 3.3 Управление ветками
- 3.4 Работа с ветками
- 3.5 Удалённые ветки
- 3.6 Перебазирование
- 3.7 Заключение
-
4. Git на сервере
- 4.1 Протоколы
- 4.2 Установка Git на сервер
- 4.3 Генерация открытого SSH ключа
- 4.4 Настраиваем сервер
- 4.5 Git-демон
- 4.6 Умный HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Git-хостинг
- 4.10 Заключение
-
5. Распределённый Git
-
6. GitHub
-
7. Инструменты Git
- 7.1 Выбор ревизии
- 7.2 Интерактивное индексирование
- 7.3 Припрятывание и очистка
- 7.4 Подпись
- 7.5 Поиск
- 7.6 Перезапись истории
- 7.7 Раскрытие тайн reset
- 7.8 Продвинутое слияние
- 7.9 Rerere
- 7.10 Обнаружение ошибок с помощью Git
- 7.11 Подмодули
- 7.12 Создание пакетов
- 7.13 Замена
- 7.14 Хранилище учётных данных
- 7.15 Заключение
-
8. Настройка Git
- 8.1 Конфигурация Git
- 8.2 Атрибуты Git
- 8.3 Хуки в Git
- 8.4 Пример принудительной политики Git
- 8.5 Заключение
-
9. Git и другие системы контроля версий
- 9.1 Git как клиент
- 9.2 Переход на Git
- 9.3 Заключение
-
10. Git изнутри
- 10.1 Сантехника и Фарфор
- 10.2 Объекты Git
- 10.3 Ссылки в Git
- 10.4 Pack-файлы
- 10.5 Спецификации ссылок
- 10.6 Протоколы передачи данных
- 10.7 Обслуживание репозитория и восстановление данных
- 10.8 Переменные окружения
- 10.9 Заключение
-
A1. Приложение A: Git в других окружениях
- A1.1 Графические интерфейсы
- A1.2 Git в Visual Studio
- A1.3 Git в Visual Studio Code
- A1.4 Git в Eclipse
- A1.5 Git в IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.6 Git в Sublime Text
- A1.7 Git в Bash
- A1.8 Git в Zsh
- A1.9 Git в PowerShell
- A1.10 Заключение
-
A2. Приложение B: Встраивание Git в ваши приложения
- A2.1 Git из командной строки
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Приложение C: Команды Git
- A3.1 Настройка и конфигурация
- A3.2 Клонирование и создание репозиториев
- A3.3 Основные команды
- A3.4 Ветвление и слияния
- A3.5 Совместная работа и обновление проектов
- A3.6 Осмотр и сравнение
- A3.7 Отладка
- A3.8 Внесение исправлений
- A3.9 Работа с помощью электронной почты
- A3.10 Внешние системы
- A3.11 Администрирование
- A3.12 Низкоуровневые команды
7.13 Инструменты Git - Замена
Замена
Объекты в Git неизменяемы, но он предоставляет интересный способ эмулировать замену объектов в своей базе другими объектами.
Команда replace
позволяет вам указать объект Git и сказать «каждый раз, когда встречается этот объект, заменяй его другим».
В основном, это бывает полезно для замены одного коммита в вашей истории другим.
Например, допустим в вашем проекте огромная история изменений и вы хотите разбить ваш репозиторий на два — один с короткой историей для новых разработчиков, а другой с более длинной историей для людей, интересующихся анализом истории. Вы можете пересадить одну историю на другую, «заменяя» самый первый коммит в короткой истории последним коммитом в длинной истории. Это удобно, так как вам не придётся по-настоящему изменять каждый коммит в новой истории, как это вам бы потребовалось делать в случае обычного объединения историй (так как родословная коммитов влияет на SHA-1).
Давайте испробуем как это работает, возьмём существующий репозиторий и разобьём его на два — один со свежими правками, а другой с историческими, и затем посмотрим как мы можем воссоединить их с помощью операции replace
, не изменяя при этом значений SHA-1 в свежем репозитории.
Мы будем использовать простой репозиторий с пятью коммитами:
$ git log --oneline
ef989d8 Fifth commit
c6e1e95 Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
Мы хотим разбить его на два семейства историй. Одно семейство, которое начинается от первого коммита и заканчивается четвёртым, будет историческим. Второе, состоящее пока только из четвёртого и пятого коммитов — будет семейством со свежей историей.
Создать историческое семейство легко, мы просто создаём ветку с вершиной на нужном коммите и затем отправляем эту ветку как master
в новый удалённый репозиторий.
$ git branch history c6e1e95
$ git log --oneline --decorate
ef989d8 (HEAD, master) Fifth commit
c6e1e95 (history) Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
Теперь мы можем отправить только что созданную ветвь history
в ветку master
нашего нового репозитория:
$ git remote add project-history https://github.com/schacon/project-history
$ git push project-history history:master
Counting objects: 12, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (12/12), 907 bytes, done.
Total 12 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (12/12), done.
To git@github.com:schacon/project-history.git
* [new branch] history -> master
Таким образом, наша история опубликована, а мы теперь займёмся более сложной частью — усечём свежую историю. Нам необходимо перекрытие, так чтобы мы смогли заменить коммит из одной части коммитом из другой, то есть мы будем обрезать историю, оставив четвёртый и пятый коммиты (таким образом четвёртый коммит будет входить в пересечение).
$ git log --oneline --decorate
ef989d8 (HEAD, master) Fifth commit
c6e1e95 (history) Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
В данном случае будет полезным создать базовый коммит, содержащий инструкции о том как раскрыть историю, так другие разработчики будут знать что делать, если они столкнулись с первым коммитом урезанной истории и нуждаются в остальной истории. Итак, далее мы создадим объект заглавного коммита, представляющий нашу отправную точку с инструкциями, а затем перебазируем оставшиеся коммиты (четвёртый и пятый) на этот коммит.
Для того, чтобы сделать это, нам нужно выбрать точку разбиения, которой для нас будет третий коммит, хеш которого 9c68fdc
.
Таким образом, наш базовый коммит будет основываться на этом дереве.
Мы можем создать наш базовый коммит, используя команду commit-tree
, которая просто берёт дерево и возвращает SHA-1 объекта, представляющего новый сиротский коммит.
$ echo 'Get history from blah blah blah' | git commit-tree 9c68fdc^{tree}
622e88e9cbfbacfb75b5279245b9fb38dfea10cf
Примечание
|
Команда |
Хорошо.
Теперь когда у нас есть базовый коммит, мы можем перебазировать нашу оставшуюся историю на этот коммит используя git rebase --onto
.
Значением аргумента --onto
будет SHA-1 хеш коммита, который мы только что получили от команды commit-tree
, а перебазируемой точкой будет третий коммит (родитель первого коммита, который мы хотим сохранить, 9c68fdc
):
$ git rebase --onto 622e88 9c68fdc
First, rewinding head to replay your work on top of it...
Applying: fourth commit
Applying: fifth commit
Таким образом, мы переписали нашу свежую историю поверх вспомогательного базового коммита, который теперь содержит инструкции о том, как при необходимости восстановить полную историю. Мы можем отправить эту историю в новый проект и теперь, когда люди клонируют его репозиторий, они будут видеть только два свежих коммита и после них базовый коммит с инструкциями.
Давайте представим себя на месте кого-то, кто впервые клонировал проект и хочет получить полную историю. Для получения исторических данных после клонирования усечённого репозитория, ему нужно добавить в список удалённых репозиториев исторический репозиторий и извлечь из него данные:
$ git clone https://github.com/schacon/project
$ cd project
$ git log --oneline master
e146b5f Fifth commit
81a708d Fourth commit
622e88e Get history from blah blah blah
$ git remote add project-history https://github.com/schacon/project-history
$ git fetch project-history
From https://github.com/schacon/project-history
* [new branch] master -> project-history/master
Теперь у этого пользователя его собственные свежие коммиты будут находиться в ветке master
, а исторические коммиты в ветке project-history/master
.
$ git log --oneline master
e146b5f Fifth commit
81a708d Fourth commit
622e88e Get history from blah blah blah
$ git log --oneline project-history/master
c6e1e95 Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
Для объединения этих веток вы можете просто вызывать git replace
, указав коммит, который вы хотите заменить, и коммит, которым вы хотите заменить первый.
Так мы хотим заменить «четвёртый» коммит в основной ветке «четвёртым» коммитом из ветки project-history/master
:
$ git replace 81a708d c6e1e95
Если теперь вы посмотрите историю ветки master
, то должны увидеть нечто подобное:
$ git log --oneline master
e146b5f Fifth commit
81a708d Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
Здорово, не правда ли? Не изменяя SHA-1 всех коммитов семейства, мы можем заменить один коммит в нашей истории совершенно другим коммитом и все обычные утилиты (bisect
, blame
и т. д.) будут работать как от них это и ожидается.
Интересно, что для четвёртого коммита SHA-1 хеш выводится равный 81a708d
, хотя в действительности он содержит данные коммита c6e1e95
, которым мы его заменили.
Даже если вы выполните команду типа cat-file
, она отобразит заменённые данные:
$ git cat-file -p 81a708d
tree 7bc544cf438903b65ca9104a1e30345eee6c083d
parent 9c68fdceee073230f19ebb8b5e7fc71b479c0252
author Scott Chacon <schacon@gmail.com> 1268712581 -0700
committer Scott Chacon <schacon@gmail.com> 1268712581 -0700
fourth commit
Помните, что настоящим родителем коммита 81a708d
был наш вспомогательный базовый коммит (622e88e
), а не 9c68fdce
как это отмечено здесь.
Другое интересное замечание состоит в том, что информация о произведённой замене сохранена у нас в ссылках:
$ git for-each-ref
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/heads/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/remotes/history/master
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/HEAD
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/replace/81a708dd0e167a3f691541c7a6463343bc457040
Следовательно можно легко поделиться заменами — для этого мы можем отправить их на наш сервер, а другие люди могут легко скачать их оттуда.
Это не будет полезным в случае если вы используете replace
для пересадки истории (так как в этом случае все люди будут скачивать обе истории, тогда зачем мы разделяли их?), но это может быть полезным в других ситуациях.