Пользователь
0,0
рейтинг
20 ноября 2009 в 14:08

Разработка → ХХ полезных советов для пользователей Git среднего уровня. Часть 1

Git*
Вообще-то изначально я планировал перевести статью Энди Джеффриса (Andy Jeffries) 25 Tips for Intermediate Git Users, но в процессе я отбросил бестолковые, общеизвестные или самые простые советы вроде «настройте первым делом user.name и user.email», которые явно не подходят людям, уже более-менее плотно знакомым с Git.
Взамен я дополню статью моментами из личной практики («Своя практика»! Звучит здорово, будто я частный врач или адвокат! :-] )



1. Самое начало.


Поскольку user.{name,email,signingkey} настроены, можно посмотреть в сторону мелких удобств, вроде $ git config --global help.autocorrect 1 — в случае опечаток, гит попробует угадать что же вы имели в виду
[solar@hasher couchdb]$ git statu
WARNING: You called a Git command named 'statu', which does not exist.
Continuing under the assumption that you meant 'status'
in 0.1 seconds automatically…
# On branch 0.10.x-alt
nothing to commit (working directory clean)

Значение 1 здесь показывает десятые доли секунды.
Отрицательное значение приведёт к выполнению предполагаемой команды немедленно, а нулевое — просто к показу подсказки (по умолчанию).

Из альясов я добавляю только
git config --global alias.logp 'log --pretty=format:"%h — %an: %s"' — 10 последних коммитов в формате «SHA — Author: Commit message»
git config --global alias.unstage 'reset HEAD'
git config --global alias.remotes 'remote -v' — Это сделает вывод git remote чуть более подробным, но не столь много словным, как git remote show $branch, в котором, к тому же, нужно указать конкретный сервер.

2. Конфликты при мердже


При имеющем место конфликте можно посмотреть предмет раздора в нормальном стандартного diff -u:
$ git diff --merge
diff --cc dummy.rb
index 5175dde,0c65895..4a00477
--- a/dummy.rb
+++ b/dummy.rb
@@@ -1,5 -1,5 +1,5 @@@
class MyFoo
def say
- puts "Bonjour"
- puts "Hello world"
++ puts "Annyong Haseyo"
end
end


Если нужно получить файл из одного из мерджащихся бранчей, можно сделать git checkout —ours flash/foo.fla либо git checkout —theirs flash/foo.fla — это избавит от необходимости помнить какие бранчи сливаются.

Так же всегда можно посмотреть различия перед объединением — $ git diff branch1 branch2

3. Тэги.


Чтобы удобно определять эволюционные стадии кода, надо использовать тэги.
$ git tag -m '$tag_description' $tag_name
Если из вашего гита содержимое отправляется на сборку, все можно будет собрать конкретную указанную версию. Кстати, скорее всего, нужно будет создавать тэги, подписанные Вашим gpg-ключём (git config --global user.singingkey $key_ID)

4. Новые бранчи


Как всем должно быть известно, в новый бранч можно попасть либо создаев его и переключившись ($git branch new_branch; git checkout new_branch), либо сразу — $ git checkout -b new_branch.
Кстати, переименовать текущий бранч можно с помощью git branch -m new_name, а сторонний — git branch -m current_name new_name.
Не самая часто используемая возможность, но иногда приходится и так получается всяко быстрее чем checkout -b и последующее ручное удаление старого бранча.

5. Объединение бранчей. Merge и rebase.


Чтобы поместить в один бранч изменения из другого бранча, можно использовать либо merge — m создаёт новый коммит, в котором находятся необходимые правки, либо rebase — r же переписывает историю. С одной стороны, после rebase история получается более аккуратной, с другой — это уже другая история, поэтому в случае опубликованных общеступных бранчей лучше пользоваться merge. Тем более, что за количество коммитов никто денег не берёт.

Можно посмотреть бранчи, изменения в которых еще не перенесены в текущий бранч — это можно сделать через git branch --no-merged. Такие бранчи лучше не дропать, хехе.

6. Удалённые бранчи

Удалённые — в смысле, remote :-]

Можно пушить в удалённый бранч напрямую — git push origin branch_name, если имена локального и удалённого бранчей совпадают, а можно и с помощью полной формы — git push origin localBranch:refs/heads/Remote_Branch_Name

Кстати, чтобы не указывать каждый раз имя сервера и бранч, можно создавать отслеживаемые бранчи с помощью git checkout -b myfeature origin/myfeature. Теперь, находясь в этом бранче, можно пушить/фетчить/пуллить без конкретных имен.

7. Stash


Если кто-то еще не пользуется git stash, советую обратить на эту команду пристальное внимание. Более чем удобно, занимаясь одним делом, «отложить» текущую работу в сторону и отвлечься, скажем, на срочное исправление бага, даже если он находится в другом бранче. После исправления и коммита можно преспокойно вернуться к начатому.

i. hack-hack-hack
ii. git stash
iii. fix-fix-fix
iv. git commit -a -m 'bugfix #31337'
v. git stash pop

Те же, кто знает про git stash, посмотрите на последнюю строку — её отличие от apply в том, что откладываемые результаты не остаются во временном хранилище (посмотрите git stash list после нескольких примений stash!)

Если изменение было фактически законченным, можно оформить коммит не отходят от кассы — git stash save 'commit msg'

8. Поиск в истории


Если про коммит известно хоть что-то, можно попробовать его найти!
$ git log --grep=«Something in the message» — поиск по описанию коммита
$ git log -S"{% include \«header.tmpl\» %}" — поиск коммита по строке в файлах бранча. Обратите внимание на отсутствие пробела после -S
$ git log templates/header.tmpl — поиск коммитов, в которых затрагивался указанный файл.

Также можно уточнить поиск при помощи опций вроде --since=X.months.ago --until=Y.day.ago --author=andy -S«something»
Опция --all-match комбинирует параметры с помощью AND, а не дефолтного OR.

Дальше я опишу отмену и манипуляции с изменениями, интерактивный rebase, изменение файлов по всей истории (лично мне пригодилось, когда я заметил, что держу боевые пароли в основном бранче, хехе) и всякие другие мелочи ^_^

P.S.: Спасибо за такой нужный +1, %username% )
Mikhail A Pokidko @cabeza
карма
75,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (32)

  • 0
    >git config --global alias.remotes 'remote -v' — Это сделает вывод git remote чуть более подробным, но не столь много словным, как git remote show $branch, в котором, к тому же, нужно указать конкретный бранч.

    Бранч? Может, сервер?
    • 0
      да, конечно же сервер
  • 0
    >но в процессе я отбросил бестолковые, общеизвестные или самые простые советы вроде «настройте первым делом user.name и user.email», которые явно не подходят людям, уже более-менее плотно знакомым с Git.

    Зря, по-моему — общеизвестные или самые простые советы способствуют переходу из «начинающих» в «продвинутые» или даже из «интересующихся» в «продвинутые». Но все равно спасибо, добавил в закладки, к тому времени как стану начинающим :)
    • 0
      Способствуют, не спорю. Никто не рождается сразу с багажом знаний. Но и читать каждый раз про «первым делом укажите своё имя и почту», хотя пришел искать приёмы черной магии, утомительно, согласитесь.
    • +4
      translated.by/you/git-community-book/into-ru/trans/
      Как раз вот тут мы переводим (уже больше половины перевели) Git Community Book, которая чрезвычайна полезна и начинающим.
      Помогите нам сделать свободную, качественную и красивую книжку о Git!
      Это было что-то типа рекламы. :)
      • 0
        Сначала прочитаю что уже переведено, спасибо за наводку
  • 0
    Вопрос про push в удаленный бранч.
    Можно ли его делать в не-bare репозиторий с имеющимися там изменениями?
    Я хочу таким образом выкладывать код из репы на сервер, но чтобы при этом сделанные там изменения (те же самые конфиги) не затирались, а мерджились.
    Видел какие-то вариант, но все чрезмерно замороченные. Может есть нормальный удобный способ?
    • 0
      Т.е. Вы делаете коммит origin_head+1, и хотите выложить его, даже если кто-то запушил своё вариант origin_head+1?
      Вам поможет git rebase origin/master — после этого можете пушить свои изменения.

      Я бы держал свои изменения в отдельном бранче, а в мастер держал бы синхронизированным с репо на сервере, но сути это не меняет.
      • 0
        То есть перед пушем туда мне надо сделать там rebase?

        Да, пуш в мастер на удаленную машину предполагается из мастера. Но они все равно будут рассинхронизированы в части настроек (скажем логин и пароль для БД другие). При этом хотелось бы обновить удаленный репо и не затереть его настройки. Исключать конфиг из контроля версий неправильно. Перезаписывать файл поверх после пуша тоже плохо — т.к. в настройки может добавиться что-то, что не хотелось бы руками переносить.
        • 0
          Да, перед пушем на апстримный сервер надо сделать ребэйз, тем самым вы притворитесь, будто спушили всё, что можно, и свой коммит сделали с пылу с жару, основываясь на самом последнем коммите из апстрима.

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

          Или я не понял исходную задачу.
          • 0
            Просто я хочу, чтобы бранч с конфигами лежал на удаленном серваке, и автоматически примерживался, после пуша.
            У меня была мысль, что если его смержить один раз когда-то давно, с тем смыслом, чтобы гит «знал» про этот мердж, и учитывал его. Будет ли это иметь работать? Или такие изменения затрутся в истории гита? Возможно я спрашиваю ерунду — я еще не разобрался для себя как работает гит.
            • 0
              Посмотрите в сторону server-hooks. Post-commit, например
              • 0
                Спасибо.
  • 0
    # я бы посоветовал еще одну комманду:

    git pull. refs/remotes/origin/master-branch
    # смержить удаленный бранч к себе

    # ну и обратить внимание на такую последовательность:

    git push origin origin/source-branch:refs/heads/new-branch
    # создает на удаленном сервере бранч new-branch c кодом из source-branch
    git checkout -b new-branch --track origin/new-branch
    # создаль локальный new-branch и настроить его на треканье удаленного =)

    • 0
      # и да, забыл
      git push origin :heads/new-branch
      # удалить созданный бранч в удаленном репозитории
      • 0
        без ":heads/" тоже должно работать, не?
        • 0
          да, работает
    • 0
      Git, c версии 1.6.2, кажется, пытается делать track`уемым бранч, если он был создан через -b.

      Я укажи эти команды во второй части статьи, спасибо
      • 0
        там только точку не отделил в первой комманде
        «git pull . refs/remotes/origin/master-branch»
        а вообще гит — мощная штука, и наличие например «git commit --amend» очень порадовало.
        • 0
          git commit -a --amend -C HEAD более чем удобно, да!

          Но, честно сказать, меня больше порадовало недавно примененная
          $ git filter-branch --tree-filter «sed -e 's#my_secret_pass#dbpassword#' -e 's#my_db_hostdbhost.tld#' -i settings.py» HEAD
          где my_secret_pass и my_db_host были реальными паролем и хостом ^_^
  • +1
    еще один полезный алиас из ~/.gitconfig
    here = "!echo $(git symbolic-ref HEAD | sed 's/refs\\/heads\\///g')"

    позволяет выполнять комманды типа
    git push origin `git here` # пушнуть изменения в ветку удаленного репозитория

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

    в общем алиасов хороших можно привести много — главное не боятся их писать — тем более что в гите их делать проще простого.

    • +1
      > git push origin `git here` # пушнуть изменения в ветку удаленного репозитория

      это --track!))
  • 0
    Из недавно освоенных полезных трюков:

    git add -i # Интерактивное добавление файла в индекс
    git add -p # Интерактивное добавление файла в индекс с выбором отдельных ханков (частный случай -i)
    git rebase -i # Трансплантация ветки с редактированием состава коммитов (можно пропускать, редактировать, объединять)

    • +1
      Про rebase -i я еще расскажу!
      Всегда интересовало — в каких случаях интерактивно, скажем, выборочно добавлять файл?
      • 0
        * в каких случаях нужно
      • +3
        чем атомарнее коммит — там легче мёрж. так как коммит происходит на локальной машине без оверхеда работы по сети — то коммиты можно делать хоть раз в 5 минут
        но иногда закапываешься в какую-то проблему, попутно исправляешь какие-то баги — в итоге в файле накапливаются изменения не связаные друг — с другом.
        в таком случае лучше закоммитить по ханкам — всё что относится к одной фиче — в один коммит, что к другой — в другой коммит.
        кроме того, очень полезная комманда: git cherry-pick. использовать можно так — есть бранч, в котором новая фича и его нельзя пока мержить в мастер. Но там попутно исправлено пару багов, а вас просят влить эти исправления в мастер. Делается в бранче сделаном от мастера
        git cherry-pick BUGFIX_COMMIT_SHA1
        и не надо заново вносить изменения или как-то менять историю (в этом случае вы подумаете, что «как хорошо, что я дулал коммиты атомарными и этот багфикс не тянет за собой кучу изменений связаных с новой фичей»)
        • 0
          Понятно
          Никак не могу заставить себя делать по-настоящему атомарные коммиты
      • +1
        Я так делаю, к примеру, когда исправляю ошибку: вношу сами изменения с комментариями «для себя» или часто исправляю несколько ошибок или, к примеру, добавляю команды вывода в логи. И вот что бы сделать патчсеты с корректными названиями я и делаю git add -p. «Косметику», вроде логгирования и отладочных сообщений обычно не хочется отсылать в проекты из-за несоответствия стилей кодирования, поэтому я её потом при необходимости снова потестироваться трансплантирую наверх апстримовской ветки.
  • 0
    Неистово плюсую!
    Столько новой инфы по гиту — это же просто праздник какой-то! :-)
    • +1
      Хаха, спасибо!)
      • 0
        Это Вам спасибо!
  • +1
    Ну в придачу ещё две полезные комманды:
    git rerere — позволяет записывать решение конфликтов и при повторном мерже использовать их. Помогает в случае если бранч живет долго и часто возникает необходимость мержится с другими бранчами. Включается просто: git config --global rerere.enabled 1
    И git rebase --onto. Позволяет перенести ветку с одним основанием на другое. Когда полезно: Есть мастер в котором происходит активная разработка. В определенный момент был начат бранч с новой независимой фичей, но от текущего мастера. Фича сделана и в принципе может быть внесена в стабильную ветку, но из-за того что она была начата на от стабильной ветки, при обычном мерже потянет за собой коммиты, которые не должны попасть в стабильную версию. На выручку приходит git rebase --onto, который позволяет создать ветку которая будет начинаться от стабильной версии и содержать все коммиты, которые были сделаны в ветке от мастера.

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