ХХ полезных советов для пользователей Git среднего уровня. Часть 2

    Это продолжение статьи ХХ полезных советов для пользователей Git среднего уровня

    Про reset, незапланированно снова про альясы, про замечательный filter-branch, про мерджи и разрешение конфликтов с помощью rerere, про rebase (интерактивный и не очень) и, в завершение, про обслуживание своей гитницы.



    1. Где у Git`a reset

    Если брать самые частые случаи, когда нужно так или иначе отменить последний коммит или изменения после оного, то это будет команды git reset (она же git reset --mixed HEAD) и git reset --hard. Первая удаляет из индекса последний коммит, но не трогает изменения в файлах, вторая же беспощадно приводит и индекс, и файлы к первозданному виду^W^Wсостоянию на момент указанного коммита.
    Иными словами, отменить правки, пусть даже и за`stage`нные через git add, можно сделав $ git reset HEAD (помните альяс git unstage?).
    А полностью дропнуть, скажем, последний коммит (т.е. отмотать время на момент предпоследнего коммита) — $ git reset --hard HEAD^.

    2. Еще альясы

    По итогам изучения history|grep " git "| sort -d|uniq, я добавил еще один альяс — $ git config --global alias.amend 'commit --amend -C HEAD' — по команде git amend перезаписывается последний коммит с тем же сообщением.
    Возможно, добавлю туда ключ -a (чтобы не делать git add каждый раз).

    3. filter-branch — спасение для растяпы!

    git filter-branch позволяет более чем широкие возможности для манипуляций с историей.
    Я познакомился с этой командой, когда заметил, что храню боевые пароли не во внутренней ветке, а в мастере.
    Слава Богу, что заметил я это перед пушем в общедоступный репозитарий!
    $ git filter-branch --tree-filter «sed -e 's#my_secret_pass#dbpassword#' -e 's#my_db_hostdbhost.tld#' -i settings.py» HEAD
    Сим нехитрым действием я переписал пароли в файле настроек в каждом коммите.

    Можно сужать область применения, задавая уточняющие параметры --index-filter, --msg-filter, --commit-filter--tag-name-filter и другие.
    --all означает все бранчи.

    4. И снова про мерджи.

    Спасибо ghisguth за наводку на потенциально полезный инструмент обращения с конфликтами про слиянии веток.
    Сам я им еще не пользуюсь, но закоменченная (чтобы не забыть) строчка в конфиге уже есть.

    Итак, встречайте — git rerere — позволяет записывать решение конфликтов и при повторном мерже использовать их.
    Включается так — $ git config --global rerere.enabled 1

    Полезно, если есть долгоживущий бранч разработки, который точно конфликтует с другим бранчем (скажем, мастером), то «обучение» гита телодвижениям при мердже происходит в такой последовательности: сначала делаем git pull origin master, устраняем конфликты, коммитим и откатываем тестовое слияние обратно — $ git reset --hard HEAD^. При настоящем слиянии будут автоматически проделаны все те же телодвижения, что мы сделали руками.

    Вообще-то git rerere запускается без вмешательства пользователя и без аргументов, но можно подкорректировать ход работы:
    Команды: git rerere [clear|diff|status|gc]

    clear — Ресет метаданных, используемых rerere, если автоматическое решение конфликта отменяется.
    Например, git am [--skip|--abort] или git rebase [--skip|--abort] автоматически использует этот параметр.
    diff — Отображение диффа для текущего состояния разрешения конфликта. Полезно для отслеживания что изменилось за время устранения конфликта. дополнительные параметры передаются прямо команде diff.
    В отличие от diff, выводит только имена файлов, которые отслеживаются для разрешения конфликта.
    gc — Удаляет записи конфликтных слияний, которые имели место быть много времени назад. По умолчанию, неразрешенные конфликты старше 15ти дней и разрешенные старше 60ти дней вычищаются. Эти значения описаны в gc.rerereunresolved и gc.rerereresolved.

    5. Про rebase

    Про ребэйз уже было сказано, что эта команда позволяет «переместить» сделанные изменения наверх, поместив в историю коммиты из мастера, которые «ушли вперед» за время внесения локальных правок.

    Есть еще одно интересное применение — rebase --onto.
    Таким образом можно перенести свои коммиты, основываясь на совершенно третьей сторонней ветке.
    Например, есть такая картина:
    .-x------------master
    .   |
    .   \ -----server-experiments
    .       |
    .       \---http-interface

    Чтобы смерджить ветку с новым интерфейсом в мастер, оставив игрища с серверной частью, надо сделать
    $ git rebase --onto master server-experiments http-interface

    После этого можно переключаться в мастер и мерджить ветки (git checkout master; git merge http-interface) — по итогу у нас есть основная ветка, влитый в неё новёхонький интерфейс и совершенно отдельно — оставшаяся ветка с экспериментами над серверной частью.
    PROFIT!

    Можно ребейзить не всё подряд, а интерактивно и выборочно — для этого нужна опция -i (--interactive).
    $ git rebase -i master откроет редактор со списком указанных коммитов вида «command SHA commit_message».
    Команды бывают:
    p|pick — использовать коммит;
    e|edit — использовать коммит, но сделать паузу ради --amend (процесс остановится перед следующим пунктом, чтобы можно было внести какие-то свои особые правки — например, разбить изменение на более мелкие коммиты)
    s|squash — использовать коммит, но объединить с предыдущим коммитом. (в процессе будет открыто окно редактора, чтобы можно было соорудить общий commit message. Этакий глобальный commit --amend)

    Удаление линии приведёт к удалению коммита.

    Продолжить естественный ход вещей можно с помощью $ git rebase --continue

    6. Обслуживание

    Если идёт активная разработка и вы не пушите код на удалённые сервер, то стоит периодически выполнять сборку мусора:
    $ git gc
    Это подчистит мусор — удалит ни к чему не привязанные объекты и эффективно (пере-|у-)пакует оставшиеся.
    Я сказал про удалённые сервера, потому что перед пушем на другой сервер объекты обрабатываются автоматически.

    Вот, кажется, и всё, что я хотел бы сказать по поводу гита)
    Поделиться публикацией
    Похожие публикации
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 35
      +1
      А можете подробнее сказать про rebase? Зачем это вообще нужно?
        0
        ребэйз приводит набор ваших патчей к текущему «мастеру» или тому относительно чего вы ребэйзитесь, плюс попутно можно схлопнуть несколько мелких коммитов в один или выкинуть какие то…
          0
          А зачем это надо? Можете привести пример, когда от rebase есть польза?

          Я использую git активно, в команде, но я не могу придумать ни одной причины, по которой мне захотелось бы сделать rebase.
            0
            Например, я собираю в Альте couchdb. Я клонирую апстрим. От бранча 0.10.х ответвляю 0.10.x-alt, в котором держу альт-специфичные изменения и в который время от времени мерджу апстримный 0.10.x.
            Итого, если я захочу отослать обратно свои правки, я сделаю ребейз, смерджу изменения и запушу.
              0
              Простите, не понял. Ребейз чего с чем Вы сделаете? И зачем? Почему Вы не можете отослать свои правки без ребейза?
                0
                «отослать правки без ребейза» я могу только в виде патча.
                Либо запушить свой бранч целиком, чтобы с ним парился апстрим, что неправильно и невежливо.
                С rebase`ом же я могу сделать git rebase origin/0.10.x, находясь в 0.10.x-alt, после чего переключиться обратно на 0.10.х и смерджить свои изменения — git merge 0.10.x-alt. Т.е. сделать так, чтобы у меня на руках был бранч, опережающий origin ровно на мои правки. Таким образом, мне останется только сделать push.
                  0
                  Не понимаю, хоть убей. В чем проблема отправить бранч целиком? Что тут такого неправильного и невежливого?

                  Есть где-то развернутое описание этого дела?
                    0
                    Отправить бранч целиком означает «мне было лень привести всё в божеский вид, ковыряйтесь сами».
                    Если Вы не коммитер в этот проект, то бранч целиком у вас никто и не примет и Ваш тоже смотреть не будет.
                    А если Вы коммитер и написали какую-то новую фичу, то Вам её и вливать в основное дерево. Можно тупо смерджить — в основной истории будут все неудачные, ненужные и промежуточные коммиты вместо единообразной красивой истории, в которой добавление вашей фичи, грубо говоря, представлено один коммитом и в силу такой атомарности отрывание её не представляет труда.
                    Слишком упрощенно, но доступней я не могу, из меня объясняльщик хреновый, к сожалению.
                      0
                      Т.е. получается, все это чисто для красоты?

                      Атомарность коммита и легкость отрывания я бы понял, если бы бранч целиком сложно было оторвать. Но ведь бранч оторвать легко. Смерженный бранч — это точно такой же коммит, отрываем его — и отрывается весь бранч.
                        0
                        Здесь красота не косметическая, а сугубо утилитарная.

                        Знаете, ну как является красивым идеально написанный код — он же красив своей стройностью, а не некой внешней красотой.
                          0
                          Попробовал rebasе. Это же ужас.

                          Есть бранч. Если я его просто смержу с мастером, то потом я могу его откатить одним reset'ом или revert'ом. Возникает точка слияния и достаточно откатить эту точку, чтобы откатились все коммиты бранча.

                          Если же я сделаю rebase бранча, а потом смержу бранч с мастером, то потом же невозможно будет этот бранч откатить. Точки слияния нет, значит, придется откатывать кучу одиночных коммитов, а предварительно соображать, сколько же коммитов относятся именно к этому бранчу.

                          Это неудобно. А вот «запутанная» история разработки, о которой все говорят, никаких неудобств не вызывает. Я чего-то не понял?
                            0
                            ну этим и отличается rebase от merge
                            итог примерно один — пути разные — ибо для разных случаев применяется
                              0
                              откатить можно — git reflog и git reset --hard по нему
                        0
                        git — распределенная система. У каждого свой репозитарий. Здесь не принято отправлять бранч куда-нибудь. Это у вас его могут заpull-ить.
                          0
                          Ага, порассказывайте в апстриме какого-нибудь крупного проекта, что им надо что-то пулить с неизвестных серверов, вместо быстрого изучения приаттаченных патчей.
                          Обычно format-patch и вперед.
                            0
                            Мммм… Большой проект. Да да, припоминаю. Линус Торвальдс постоянно pull-ит из репоизитариев своих помощников.
                              0
                              Вот именно, мерджится с своими офицерами и только с ними, а вовне со всеми подряд. Ваш правки дойдут до них (офицеров) через N-ное количество промежуточных бранчей.

                              Я, наверное, резковато откаментил — я имел в виду, что первоначально в проект коммиты в 90% случаев попадут в виде патчей, а не кто-то их будет пулить.
                              Разве что это совсем вменяемый и не самый крупный апстрим.
                                0
                                Да, я согласен. Своим первым комментарием я ставил целью объяснить, что подход ivanych-а является подходом централизованныз систем контроля версий, когда используется единый главный репозиторий, в который все «толкают» свой код. Он всего лишь перенес эти подходы на git, не понимая, как мне кажется, саму идею распределенности.
                                  0
                                  Мне кажется, Вы не поняли, что именно я написал.

                                  У меня нет единого репозитория, в который все толкают свой код. Репозиторий dev — у меня это не единый репозиторий. У меня у каждого разработчика свой собственный публичный репозиторий, именно туда каждый разработчик и толкает свой код. А я, апстрим, только стягиваю из ихних репозиториев устраивающие меня правки. Это ровно та же схема, про которую Вы чуть выше рассказывали касательно Торвальдса.

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

                                  И да, в этом репозитории нет единой ветки, в которую все бесконтрольно пушат. Об этом, собственно, вся статья и написана.
                                    0
                                    Ваша модель работает, это я прекрасно понимаю. Наверное я погорячился, назвав ее неправильной, ведь git не задает никаких ограничений в использовании и даже советов не дает по тому, как использовать. Просто такую нестандартную модель я еще не встречал.

                                    Идеальной по моему мнению является модель, когда «релизный» репозиторий находится у главного архитектора\менеджера и он постепенно берет патчи\пуллит изменения от разработчиков. А сами разработчики, при необходимости, берут что-то у своих коллег или с этого «релизного» репозитория. Примерно такая модель в Linux-е.
                                      0
                                      Так именно так у меня все и есть. Вы все-таки перечитайте еще раз:)
                          0
                          Если говорить про самый базовый случай, то делая rebase вы берете на себя и свой бранч вопрос разруливания конфликтов и облегчаете жизнь мейнтейнерам master'а(к примеру) — во время git merge yoursuperbranch все конфликты уже разрешены.
                          А если говорить про git rebase -i это уже совершенно новая ипостась — там можно склеивать коммиты, убирать их, изменять их последовательность, редактировать коммитмесседжи и много чего еще. Для собственных фича-бранчей — превосходный набор функционала.
                    0
                    сейчас как раз пишу о том как мы этим пользуемся в проекте, выложу как закочу.
                    если коротко — получается более красивая история в гите.
                      0
                      Спасибо, жду.
                      0
                      Еще пример — если вам приходится синхронизироваться с svn репозиторием. Svn не понимает веток git поэтому там мерджи выглядят как один большой коммит. Делая rebase можно сохранить всю историю коммитов из сливаемой ветки.
                      0
                      в не подскажете алгоритм, как «схлопнуть» несколько произвольных коммитов в текущей ветке в один?
                      Пример:
                      Допустим у нас есть коммутативное множество коммитов A | B | C| D | E
                      Каким способом можно из него получить эквивалентное множество вида B | (A+C+D) | E или A | (B+C) | D | E или хотя бы A | B | (C+D+E).
                        0
                        Ну git squash, но это, ясен пень, только для незапушенного, а в запушенном кто ж даст историю менять без push -f
                          0
                          git rebase -i SHA и далее следуете инструкциям, там кроме squash еще и fixup в свое время появился. Все указанные Вами комбинации легко реализуемы, кроме того коммиты можно удалять.
                        0
                        Это позволяет иметь прямую историю, в одноу ветку «мастер».

                        Например, вы ветвитесь где-нибудь. В это время происходят коммиты и в мастер, в вашу ветку. Можно слить ветки в одну — но это усложняет историю.

                        А можно наложить коммиты вашей ветки поверх коммитов мастера. Получит прямая история.
                          0
                          ну вобщем да, выше сказано
                          если не понятно, то вот пример:
                          1. забираем текущее дерево
                          2. создаём ветку experimental и что-то там правим
                          3. в это время в апстрим что-то закоммитили
                          4. чтобы всё гладко смерджилось, обновляем свой мастер, через rebase «освежаем» проделанную работу и тогда уже мерджим свои наработки в master.
                          0
                          Спасибо! Хочу свичнуться на git полностью, поэтому сразу в закладки!
                            0
                            Не понимаю одной вещи: как мне применить все коммиты из одной ветки в другую кроме одного или двух? Если делать git rebase -i branch master и удалить записи о ненужных коммитах, то они соответственно удаляться из branch и из master, а мне нужно чтобы в branch эти два коммита остались. Остается cherry-pick — но если по одному коммиты перечислять — это как-то медленно.
                              0
                              хм
                              rebase и есть средство подчистить историю перед мерджем, т.ч. коммиты логично удаляются.

                              Если не хочется терять эти коммиты, то, наверное, да, только черри-пикать.
                              Как вариант, git merge --no-commit, но я сомневаюсь, что это будет быстрее-легче, чем cherry-pick.

                              Может отбранчевать ветку, сделать в ней rebase с удалением неугодных коммитов и после этого окончательно смерджить с мастером? С одной стороны, это действительно проще всего, с другой — расхождение между branch и master будет только увеличиваться.
                              0
                              Спасибо. Про filter-branch не знал… про rerere знал, но ни разу не пользовался.
                                –1
                                Предлагаю добавить git kekeke — заполнение удалённого репозитория тысячами маленьких вредных коммитов.

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

                              Самое читаемое