Git
Chapters ▾ 2nd Edition

5.3 Git в разпределена среда - Управление на проект

Управление на проект

След като разгледахме как се допринася в проект по ефективен начин, вероятно ще ви интересува и обратната страна, как да поддържаме собствен такъв. Това може да включва приемане и прилагане на пачове генерирани през format-patch и изпратени до вас или пък интегриране на промени в отдалечени клонове за хранилища добавени като remotes към проекта ви. Независимо дали поддържате canonical хранилище или искате да помогнете проверявайки и одобрявайки пачове, трябва да знаете как да приемате работа от колегите ви по начин ясен за тях и удобен за вас във времето.

Работа в Topic клонове

Когато мислите да интегрирате новополучени промени, добра идея е да ги изпробвате в topic клон — временен такъв създаден специално за теста. По такъв начин е лесно да поправите пач индивидуално и да го зарежете, ако той не работи, докато имате време да го разгледате по-подробно. Ако създадете клон с име съответстващо на темата на изпратената работа, например ruby_client или нещо подобно, можете лесно да го запомните и по-късно да се върнете в него. Поддръжникът на Git проекта например, дори се стреми да използва namespaces за тези имена — като sc/ruby_client, където sc е съкращение за човека, който е изпратил работата. Както помним, създаването на клон базиран на master се прави така:

$ git branch sc/ruby_client master

Или, ако искате да превключите към него веднага, може да използвате checkout -b варианта:

$ git checkout -b sc/ruby_client master

Сега сте готови да добавите новата работа, която сте получили, в този нов topic клон и да решите дали искате да я слеете в long-term клоновете си.

Прилагане на пачове от Email

Ако сте получили пач по имейл, ще трябва да го приложите в topic клона и да го изпробвате. Това се прави с помощта на една от двете команди git apply или git am.

Прилагане на пач с apply

Ако сте получили пача от някого, който го е генерирал с git diff или някакъв вариант на Unix diff инструмента (което не е препоръчително, вижте следващата секция), можете да го приложите с командата git apply. Ако сте записали пача в /tmp/patch-ruby-client.patch:

$ git apply /tmp/patch-ruby-client.patch

Това модифицира файловете в работната директория. Командата е почти идентична на patch -p1, въпреки че е по-параноична и приема по-малко fuzzy matches от patch. Тя също така обработва добавянето, изтриването и преименуването на файлове, ако тези процеси са правилно описани в git diff формата, нещо което patch няма да направи. Освен това git apply използва модела “apply all or abort all”, където или всичко се прилага успешно или нищо не се прилага. За разлика от нея, patch може да прилага частично patchfiles, оставяйки работната ви директория в странен статус. git apply като цяло е много по-консервативна от patch. Процесът няма автоматично да ви направи къмит — след като завърши ще трябва сами да индексирате и къмитнете промените.

Можете също да използвате git apply за да видите дали пачът ще се приложи коректно преди в действителност да се изпълни, командата е git apply --check с името на пача:

$ git apply --check 0001-seeing-if-this-helps-the-gem.patch
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply

Ако няма изход, тогава пачът би следвало да се приложи чисто. В допълнение, командата завършва с код за грешка, ако проверката установи неуспех, така че можете да я използвате и в скриптове, ако желаете.

Прилагане на пач с am

Ако работата идва от напреднал Git потребител, който е наясно с format-patch, тогава работата ви се улеснява, защото пачът ще съдържа също и информация за автора и къмит съобщение. Ако можете, окуражавайте сътрудниците ви да използват format-patch вместо diff, когато генерират пачове за вас. Използвайте git apply само за legacy пачове и неща като това (diff генерирани).

За да приложите пач генериран с format-patch, по-добрият вариант е да използвате git am (наречена е am понеже се използва за "apply a series of patches from a mailbox"). Технически, git am е проектирана да чете mbox файл, който е plain-text формат за съхранение на едно или повече имейл съобщения в един файл. Изглежда по подобен начин:

From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] add limit to log function

Limit log functionality to the first 20

Това е началото на изхода от командата git format-patch, който видяхме в предната секция, също валиден mbox имейл формат. Ако някой ви е изпратил пача по пощата коректно с git send-email и го запишете в mbox формат, тогава можете да подадете на git am въпросния mbox файл и тя ще започне да прилага всички пачове, които намери. Ако имате имейл клиент способен да записва множество съобщения в mbox формат, тогава можете да запишете цялата серия пачове в един файл и след това да пуснете git am, която да ги приложи последователно.

Обаче, ако някой е качил пач файл генериран с git format-patch към ticketing система или нещо подобно, можете да запишете файла локално и след това да го подадете на git am да го приложи:

$ git am 0001-limit-log-function.patch
Applying: add limit to log function

Може да видите, че той се е приложил чисто и автоматично се създава нов къмит за вас. Информацията за автора е взета от хедърите From и Date на имейла, а къмит съобщението от Subject хедъра и тялото му (частта преди пача). Например, ако този пач беше приложен от mbox примера отгоре, генерираният къмит би изглеждал така:

$ git log --pretty=fuller -1
commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0
Author:     Jessica Smith <jessica@example.com>
AuthorDate: Sun Apr 6 10:17:23 2008 -0700
Commit:     Scott Chacon <schacon@gmail.com>
CommitDate: Thu Apr 9 09:19:06 2009 -0700

   add limit to log function

   Limit log functionality to the first 20

Commit реда индикира кой е човекът, който е приложил пача и времето, когато това е станало. Author от своя страна носи информация за създателя на пача и кога е създаден първоначално.

Възможно е обаче пачът да не се прилага чисто. Може главният ви клон да се е разделил твърде много от клона, от който е направен пача или пък самият пач да зависи от друг такъв, който все още не сте приложили. В подобен случай git am ще прекъсне и ще ви попита как искате да продължите:

$ git am 0001-seeing-if-this-helps-the-gem.patch
Applying: seeing if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Patch failed at 0001.
When you have resolved this problem run "git am --resolved".
If you would prefer to skip this patch, instead run "git am --skip".
To restore the original branch and stop patching run "git am --abort".

Командата поставя маркери за конфликт в съответните файлове, както това става при конфликтно сливане или пребазиране. Разрешаването на конфликтите също е аналогично — редактирате файла, индексирате го и след това изпълнявате git am --resolved за да продължите цикъла със следващия пач:

$ (fix the file)
$ git add ticgit.gemspec
$ git am --resolved
Applying: seeing if this helps the gem

Ако искате Git да се опита да разреши конфликта по малко по-интелигентен начин, може да подадете флага -3 и Git ще се опита да направи three-way сливане. Тази опция е забранена по подразбиране, защото няма да работи в случай, че пачът рапортува, че е базиран на къмит, който не присъства в хранилището ви. Ако обаче имате този къмит (ако пачът например е базиран на публичен такъв), тогава -3 опцията е много по-гъвкава в прилагането на конфликтен пач:

$ git am -3 0001-seeing-if-this-helps-the-gem.patch
Applying: seeing if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
No changes -- Patch already applied.

В този случай, без -3 флага пачът щеше се счита за конфликтен. Но понеже той е подаден, пачът се прилага чисто.

Ако прилагате множество от пачове от mbox, можете също така да пуснете am командата в интерактивен режим, при което тя ще спира на всеки пач и ще ви пита дали желаете да го приложи:

$ git am -3 -i mbox
Commit Body is:
--------------------------
seeing if this helps the gem
--------------------------
Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all

Това е полезно при много пачове, защото можете да видите всеки от тях, ако сте забравили за какво е или да го откажете, ако вече е приложен.

Когато всички пачове са приложени и къмитнати в topic клона, може да изберете дали и кога да ги интегрирате в long-term клон.

Извличане от отделечени клонове

Работата на вашите колеги може и да не идва по имейл. Те могат да имат свои собствени онлайн хранилища, да са извършили много промени и да са ви пратили URL до хранилището и клона, където промените се пазят. В случаи като този, можете да добавите отдалечените хранилища и да сливате локално вместо да пачвате.

Ако Jessica ви съобщи, че има нова функционалност в клона ruby-client на хранилището ѝ, можете да тествате бързо добавяйки го като remote референция и извличайки клона локално:

$ git remote add jessica git://github.com/jessica/myproject.git
$ git fetch jessica
$ git checkout -b rubyclient jessica/ruby-client

Ако впоследствие тя ви съобщи за нова функционалност в отделен клон, можете директно да направите fetch и checkout, защото вече отдалеченото хранилище е конфигурирано при вас.

Това е най-полезно, ако работите сравнително често с даден колега. Ако някой иска да ви изпрати само единичен пач и няма намерение за продължително сътрудничество, тогава имейл методът вероятно е по-бързо решение и няма нужда колегата ви да поддържа онлайн хранилище. Освен това едва ли бихте искали да имате стотици remotes, всяко от които допринася само с един-два пача. Обаче, скриптовете и хостнатите публични услуги могат да улеснят това — зависи най-вече от това как разработвате вие и как колегите ви.

Друго предимство на този подход е, че получавате историята на къмитите. Въпреки, че може да имате merge проблеми, вие все пак знаете на коя точка от историята ви е базирана работата на колегата и правилното three-way сливане е по подразбиране вместо да трябва да подавате -3 и да се надявате, че пачът е бил генериран на публичен къмит, до който имате достъп.

Ако не работите често с определен колега, но въпреки това искате да получавате работата му по този начин, можете да подадете на git pull директно адреса на отдалеченото хранилище. По този начин правите one-time pull и не съхранявате URL-а като remote референция:

$ git pull https://github.com/onetimeguy/project
From https://github.com/onetimeguy/project
 * branch            HEAD       -> FETCH_HEAD
Merge made by the 'recursive' strategy.

Изследване на промените

Сега имате topic клон с новата работа от колега. На този етап може да определите какво бихте искали да правите с нея. Тази секция преглежда няколко команди, които ви помагат да разберете какво точно ще бъде въведено в главния клон, ако решите да направите сливане на topic клона.

Често е полезно да имате представа за всички къмити, които са налични във временния клон, но все още не са в главния. Можете да извадите къмитите в master клона добавяйки опцията --not преди името му. Това прави същото като формата master..contrib, който видяхме по-рано. Например, ако вашият колега ви изпрати два пача и създадете клон contrib, в който сте ги приложили, може да изпълните това:

$ git log contrib --not master
commit 5b6235bd297351589efc4d73316f0a68d484f118
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Oct 24 09:53:59 2008 -0700

    seeing if this helps the gem

commit 7482e0d16d04bea79d0dba8988cc78df655f16a0
Author: Scott Chacon <schacon@gmail.com>
Date:   Mon Oct 22 19:38:36 2008 -0700

    updated the gemspec to hopefully work better

Ако ви трябват и промените, които въвежда всеки къмит, можете да подадете флага -p към git log и тя ще ви изведе в допълнение diff информацията за всеки от къмитите.

За да видите пълен diff на това какво ще се случи, ако слеете този topic клон с друг, може да се наложи да използвате малък трик, за да получите коректните резултати. Може би си мислите за това:

$ git diff master

Командата действително извежда diff, но той може да е заблуждаващ. Ако master клонът се е придвижил напред след като сте създали topic клона от него, тогава ще получите изглеждащи странно резултати. Това се случва, защото Git директно сравнява snapshot-а на последния къмит от topic клона, в който сте и snapshot-а на най-новия къмит от master клона. Ако например сте добавили ред във файл от master след създаването на topic клона, директното сравнение на snapshot-ите ще изглежда така сякаш при сливане topic клона ще изтрие този ред.

Ако master е директен родител на topic клона това не е проблем. Но ако двете истории са се разклонили, тогава diff изходът ще показва, че добавяте всичките нови промени от topic и изтривате всичко уникално за master клона.

Но това, което наистина искате да видите, са промените добавени в topic — работата, която ще бъде въведена при сливането му в master. Начинът да получите този резултат е да накарате Git да сравни последния къмит в topic клона с първия общ предшественик от master.

Технически това може да направите като изрично установите кой е този предшественик и след това изпълните diff към него:

$ git merge-base contrib master
36c7dba2c95e6bbb78dfa822519ecfec6e1ca649
$ git diff 36c7db

или по-съкратено:

$ git diff $(git merge-base contrib master)

И понеже нито един от тези два начина не е достатъчно удобен, Git осигурява още едно съкратено изписване за същия резултат: triple-dot синтаксиса. В контекста на командата git diff, можете да поставите три точки между имената на два клона и тогава Git ще изпълни diff между последния къмит на клона отдясно в израза (contrib) и най-актуалния му общ предшественик от клона вляво (master):

$ git diff master...contrib

Така получавате само работата, която текущия topic клон въвежда от последния общ родителски къмит в master. Това е един много полезен синтаксис и си заслужава да се запомни наизуст.

Интегриране на получена работа

Когато работата в topic клона е готова да бъде изпратена към един или няколко mainline клонове, възниква въпроса как да го направите. Дръг въпрос е какъв да е принципния работен процес, който да използвате за поддръжка на проекта си? Има множество варианти и ще разгледаме някои от тях.

Merging работни процеси

Един простичък процес за действие е просто да слеете цялата нова работа директно в master клона. В такъв сценарий се предполага, че имате master клон, който съдържа стабилен код. Когато получите промени в topic клон и смятате, че той е готов за интегриране или пък когато сте получили и проверили работата на колега, просто сливате нещата в master клона, изтривате topic клона и това се повтаря във времето.

Например, ако имаме хранилище с работа в два клона, ruby_client и php_client, изглеждащи като в История с няколко topic клона. и слеем ruby_client последван от php_client, историята накрая ще изглежда като След сливане на topic клон..

История с няколко topic клона.
Figure 73. История с няколко topic клона.
След сливане на topic клон.
Figure 74. След сливане на topic клон.

Вероятно това е най-простият работен процес, но може да бъде проблематичен, ако си имате работа с по-големи или по-стабилни проекти, където искате да сте много внимателни с това, което въвеждате като промени.

Ако проектът е особено важен, може да искате да използвате двустъпков цикъл на сливане. При такъв сценарий, имате два long-running клона, master и develop, при които master се обновява само, когато излезе много стабилна версия на проекта и промените преди това са интегрирани успешно в develop. И двата клона редовно се публикуват в публично хранилище. Всеки път, когато имате нов topic клон за сливане (Преди сливане на topic клон.), вие го сливате в develop (След сливане на topic клон.), след което тагвате нова версия и правите fast-forward на master към мястото, където сега е стабилния develop (След нова версия на проекта.).

Преди сливане на topic клон.
Figure 75. Преди сливане на topic клон.
След сливане на topic клон.
Figure 76. След сливане на topic клон.
След нова версия на проекта.
Figure 77. След нова версия на проекта.

По такъв начин, когато някой клонира хранилището, може да извлече или master и да компилира последната стабилна версия, или develop, който съдържа най-пресните новости. Можете допълнително да разширите тази концепция с отделен integrate клон, където цялата работа е слята заедно. След това, когато кодът в него е стабилен и преминава тестовете, го сливате в develop клона и ако времето покаже, че и той е наистина надежден, правите fast-forward на master.

Large-Merging работни процеси

Проектът Git има четири long-running клона: master, next, и pu (proposed updates) за нови промени, и maint за maintenance backports. Когато сътрудниците изпратят нова работа, тя се събира в topic клонове в хранилище на поддържащия проекта в маниер подобен на този, който описахме (вижте Управление на сложни серии от parallel contributed topic клонове.). В този момент, topic клоновете се изпитват, за да се определи дали са безопасни и готови за използване или трябва да се поправят допълнително. Ако са добре, те се сливат в next и този клон се публикува, така че всички могат да изпробват новите промени както са интегрирани заедно.

Управление на сложни серии от parallel contributed topic клонове.
Figure 78. Управление на сложни серии от parallel contributed topic клонове.

Ако промените не са задоволителни, те вместо това се сливат в клона pu. Когато се установи, че са окончателно надеждни, промените биват интегрирани в master. След това next и pu се построяват отново от master. Това значи, че master винаги се мести напред, next от време на време се пребазира, а pu се пребазира още по-често:

Сливане на topic клонове в long-term integration клонове.
Figure 79. Сливане на topic клонове в long-term integration клонове.

Когато даден topic клон най-сетне се слее в master, той се изтрива от хранилището. Проектът Git също има и клон maint, който се прави от последната версия за да осигури backported пачове в случай, че се изисква maintenance release. Така при клониране на Git хранилището, имате четири клона, които може да използвате за да пробвате проекта в различните му състояния на разработка, в зависимост от това колко актуален искате да бъде или как искате да допринасяте към него. А поддържащият проекта има структуриран работен процес за управление на новите промени. Все пак, Git проектът е със специализиран работен процес. За да го разберете напълно, можете да погледнете Git Maintainer’s guide.

Rebasing и Cherry-Picking работни процеси

Други мениджъри на проекти предпочитат да пребазират или cherry-pick-ват нови промени на базата на master клона си, вместо да ги сливат. Целта е да поддържат почти линейна история. Ако се установи, че промените в topic клона са подходящи за интегриране, превключвате към него и изпълнявате rebase командата, за да възпроизведете промените на базата на текущия master клон (или от develop и т.н.). Ако това работи добре, можете да направите fast-forward на master клона и да запазите линейната история на проекта.

Другият начин да преместите нова работа от един клон в друг е да я cherry-pick-нете. Процесът cherry-pick в Git е подобен на rebase за единичен къмит. Той взема пача, който е бил въведен в даден къмит и се опитва да го приложи повторно в текущия клон. Това е полезно, ако имате множество къмити в topic клон и искате да интегрирате само един от тях или ако къмитът е само един, но предпочитате да не изпълнявате rebase. Да кажем, че имате проект изглеждащ така:

Примерна история преди cherry-pick.
Figure 80. Примерна история преди cherry-pick.

Ако искате да издърпате къмита e43a6 в master клона, можете да направите следното

$ git cherry-pick e43a6
Finished one cherry-pick.
[master]: created a0a41a9: "More friendly message when locking the index fails."
 3 files changed, 17 insertions(+), 3 deletions(-)

Това въвежда същата промяна от e43a6, но получавате нов SHA-1 хеш за къмита, защото приложената дата е различна. Сега историята изглежда така:

История след cherry-picking на къмит в topic клон.
Figure 81. История след cherry-picking на къмит в topic клон.

Сега можете да премахнете topic клона и да изоставите къмитите, които не желаете да въвеждате.

Rerere

Ако ви се налага да правите често сливания и пребазирания или ако поддържате topic клон с по-дълъг живот, Git предлага във ваша услуга опцията известна като “rerere”.

Терминът Rerere идва от “reuse recorded resolution” — това е начин за бързо прилагане на ръчно разрешаване на конфликти. Когато rerere е активна, Git ще съхранява набор от pre- и post-images от успешни сливания и ако установи, че е налице конфликт, който изглежда подобен на такъв, който е бил разрешен в миналото, автоматично ще използва записания начин за разрешаването му без да ви занимава с него.

Тази функционалност идва в две части: конфигурационна настройка и команда. Настройката е rerere.enabled и е достатъчно да имате следното в глобалната конфигурация:

$ git config --global rerere.enabled true

Сега след като направите ръчно разрешаване на конфликт, резолюцията ще бъде запомнена в кеша и ще бъде използвана в бъдеще.

Ако е необходимо, може да комуникирате с rerere кеша с командата git rerere. Когато тя се използва самостоятелно, Git проверява базата си данни с решения за конфликти и се опитва да намери съответствие (въпреки, че това се прави автоматично, ако rerere.enabled е true). Също така има подкоманди за разглеждане на това какво ще се запише, за изтриване на конкретна резолюция от кеша и за изтриване на целия кеш. Ще разгледаме детайлно rerere в Rerere.

Тагване на версии

Ако сте решили да обявите стабилна версия на проекта, вероятно бихте желали да присъедините таг към нея, така че ако се налага по-късно да можете да я пресъздадете отново. Как да създадете таг видяхме в Основи на Git. Ако решите и да подпишете тага, тогава командата може да изглежда така:

$ git tag -s v1.5 -m 'my signed 1.5 tag'
You need a passphrase to unlock the secret key for
user: "Scott Chacon <schacon@gmail.com>"
1024-bit DSA key, ID F721C45A, created 2009-02-09

Ако правите подписване, може да се наложи да се погрижите за евентуални проблеми свързани с разпространението на публичния PGP ключ, който се използва в процеса. Поддържащият проекта на Git е решил това включвайки публичния си ключ директно като blob в хранилището и добавяйки таг, който сочи към него. За да направите и вие нещо подобно, може да установите кой е желания ключ изпълнявайки gpg --list-keys:

$ gpg --list-keys
/Users/schacon/.gnupg/pubring.gpg
---------------------------------
pub   1024D/F721C45A 2009-02-09 [expires: 2010-02-09]
uid                  Scott Chacon <schacon@gmail.com>
sub   2048g/45D02282 2009-02-09 [expires: 2010-02-09]

След това можете директно да импортирате ключа в базата данни на Git като го експортирате и прекарате през git hash-object, което ще създаде и запише нов blob обект със съдържанието в Git и ще ви върне обратно SHA-1 хеша му:

$ gpg -a --export F721C45A | git hash-object -w --stdin
659ef797d181633c87ec71ac3f9ba29fe5775b92

След като вече имате съдържанието на ключа в Git, можете да създадете таг, който сочи директно към него посредством въпросната SHA-1 стойност:

$ git tag -a maintainer-pgp-pub 659ef797d181633c87ec71ac3f9ba29fe5775b92

Ако изпълните git push --tags, тогава тагът maintainer-pgp-pub ще бъде публикуван и споделен с всички останали. Ако някой от колегите ви желае да провери таг, може директно да извлече вашия PGP ключ като издърпа blob обекта от базата данни и го импортира в PGP:

$ git show maintainer-pgp-pub | gpg --import

Впоследствие този ключ може да се използва за проверка на всички подписани от вас тагове. Също така, в таг съобщението може да включите инструкции за това как да се проверява тага и всеки би могъл да ги прочете изпълнявайки git show <tag>.

Генериране на номера на версии

Git не увеличава монотонно числата в стил v123 или нещо подобно с всеки къмит. Ето защо, ако искате да имате нещо описателно с къмитите, бихте могли да изпълните git describe върху съответните къмити. В отговор, Git генерира стринг състоящ се от името на последния таг, който е по-ранен от този къмит, последвано от броя къмити след този таг и от частичната SHA-1 стойност на описвания къмит (добавя се и префиксът "g", означаващ Git):

$ git describe master
v1.6.2-rc1-20-g8c5b85c

Така можете да експортирате snapshot или компилирана версия и да я именувате с разбираемо за хората описание. В действителност, ако компилирате Git от изходен код клониран от Git хранилището, git --version ще ви връща нещо като горното. Ако описвате къмит, който е бил тагнат директно, ще получите просто името на тага.

Командата git describe по подразбиране изисква анотирани тагове (тези създадени с флаговете -a или -s). Ако искате да се възползвате от lightweight (не-анотирани) такива, подайте ѝ опцията --tags. Може да използвате този стринг с командите git checkout или git show, въпреки че това зависи от SHA-1 стойността в края и може да не е валидно завинаги. Например, Linux kernel проекта напоследък премина от 8 на 10 символа за да гарантира SHA-1 уникалността на обектите и по този начин по-старите git describe изходни имена станаха невалидни.

Подготовка за издаване на Release

Допускаме, че искате да публикувате готова версия на продукта. Едно от нещата, които ще се наложи да направите, е да генерирате архив на най-новия snapshot за потребителите, които не ползват Git. Командата за това е git archive:

$ git archive master --prefix='project/' | gzip > `git describe master`.tar.gz
$ ls *.tar.gz
v1.6.2-rc1-20-g8c5b85c.tar.gz

Ако някой отовори този tarball ще получи актуалния snapshot на проекта ви в директория project. Можете също да съдадете zip архив по подобен начин, подавайки --format=zip опцията към git archive:

$ git archive master --prefix='project/' --format=zip > `git describe master`.zip

Сега имате tarball и zip версии на проекта, които можете да качите в уебсайт или да изпратите по имейла.

Shortlog

Време е да информирате хората от мейлинг листата ви за промените в проекта ви. Един удобен начин да получите бързо changelog на добавеното в проекта от последния път, когато сте изпратили известие, е командата git shortlog. Тя резюмира всички къмити в подадения ѝ обхват. Например, следното ще ви изведе обобщение на всички къмити от последния release насам, ако този release е бил наречен v1.0.1:

$ git shortlog --no-merges master --not v1.0.1
Chris Wanstrath (6):
      Add support for annotated tags to Grit::Tag
      Add packed-refs annotated tag support.
      Add Grit::Commit#to_patch
      Update version and History.txt
      Remove stray `puts`
      Make ls_tree ignore nils

Tom Preston-Werner (4):
      fix dates in history
      dynamic version method
      Version bump to 1.0.2
      Regenerated gemspec for version 1.0.2

Както се вижда, получавате доклад за всички къмити от v1.0.1 групирани по автор и можете да го изпратите на когото трябва.