Git
Chapters ▾ 2nd Edition

5.2 Git в разпределена среда - Как да сътрудничим в проект

Как да сътрудничим в проект

Основната трудност с обяснението на това как се допринася към проект са големия брой варианти как да го направите. Понеже Git е много гъвкав, хората могат (и го правят) да си вършат работата по различни начини – затова е проблематично да се даде съвет как да постъпите – всеки проект си има специфики. Сред факторите, които влияят са броя разработчици, избрания вид работен процес, вашите права за достъп до хранилището и вероятно външния метод за съвместна работа.

Първата неизвестна е броят разработчици – колко са много активните колеги и колко често сътрудничат? В много случаи ще имате двама или трима програмисти с малко къмити на ден, или дори и по-малко при не толкова интензивни проекти. В големи компании или проекти обаче, могат да съществуват хиляди сътрудници със стотици или хиляди къмити на ден. Това е важно, защото с увеличаването на броя програмисти, ще срещате повече проблеми с това да се уверите, че кодът се прилага чисто или може лесно да се слива. Промените, които предлагате могат да се окажат остарели или несъвместими с друга работа, която е била слята докато сте работили или докато сте чакали вашия код да бъде одобрен. Възниква въпросът как да поддържате кода си актуален и къмитите си валидни?

Следващият фактор е избраният вид работен процес. Дали той е централизиран, при който всеки разработчик има еднакъв достъп за писане до основното хранилище? Дали проектът има поддържащ потребител или интегриращ мениджър, които да проверяват всички пачове? Дали всички пачове се разглеждат и одобряват колективно? Дали вие участвате в този процес? Съществува ли lieutenant-система и трябва ли първо да изпращате работата си на вашия лейтенант?

Следват правата ви на достъп. Последователността на работа, когато трябва да участвате в проект е доста различна в зависимост от това дали имате права за писане или не. Ако нямате права за писане, как този проект предпочита да приема външна помощ? Дали въобще съществува политика за това? Какво количество работа изпращате всеки път? Колко често правите това?

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

Упътвания за къмитване

Преди да се фокусираме върху конкретни примери, едно малко отклонение касаещо къмит съобщенията. Да имате добра насока за създаване на къмити и да се придържате към нея ще направи работата ви с Git и сътрудничеството с колегите много по-лесни. Самият проект Git предоставя документ формулиращ множество добри съвети за създаване на къмити, от които да изпращате пачове — можете да го прочетете в сорс кода на Git, във файла Documentation/SubmittingPatches`.

Първо, вашите промени не трябва да съдържат никакви whitespace грешки. Git осигурява лесен начин да проверите това — преди да къмитнете, изпълнете командата git diff --check, която намира възможните whitespace грешки и ви ги показва.

Изход от `git diff --check`.
Figure 57. Изход от git diff --check.

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

След това, опитайте се да направите от всеки един къмит логически отделена, самостоятелна и специфична за даден проблем промяна. Ако можете, опитайте да правите промените си по-компактни — не програмирайте цял уикенд по пет различни задачи и после да ги изпращате като един масивен къмит в понеделник. Дори ако през уикенда не сте къмитвали, използвайте индексната област в понеделник за да разделите работата си на няколко къмита, всеки от които обслужва конкретен решен проблем и съдържа съответното подходящо съобщение. Ако някои от промените засягат един и същи файл, опитайте да използвате git add --patch за да индексирате файловете частично (показано в подробности в Интерактивно индексиране). В края на краищата, snapshot-тът на края на клона ви ще е идентичен независимо дали сте направили един или пет къмита, така че пробвайте да улесните живота на вашите колеги, когато започнат да разглеждат работата ви.

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

Последното нещо, което също не трябва да се пренебрегва, е къмит съобщението. Изработете си навик да създавате качествени къмит съобщения и ще работите с Git и колегите си много по-приятно. Като основно правило, съобщенията ви трябва да започват с един ред не по-дълъг от 50 символа, описващ стегнато промените, следван от празен ред, след което може да се разположат по-дългите обяснения. Git проектът например изисква подробното описание да включва мотивите ви за дадената промяна и да обяснява разликите с оригиналния вариант на кода — и това е добро правило, което може да следвате. Добра идея също е да използвате сегашно време и повелителен стил в съобщенията. С други думи, използвайте команди. Вместо “I added tests for” или “Adding tests for,” използвайте “Add tests for.” Тук има един шаблон, написан отначало от Tim Pope:

Кратко (50 или по-малко символа) описание на промените

По-детайлно описание, ако е нужно. Пренасяйте го
на около 72 символа. В някои случаи, първият
ред се третира като subject на email и останалото
от текста като тяло. Празният ред разделящ двете
е много важен (освен ако не пропускате тялото
изцяло);инструменти като rebase може да се объркат
ако пуснете двете заедно.

Следващите абзаци се разделят с празни редове.

  - точките (Bullet) също са ОК

  - Обикновено за точка се използва звездичка или тире,
    Предшествано от един интервал, с празни редове
    помежду им, но правилата тук варират

Ако всички ваши къмит съобщения следват този модел, нещата между вас и колегите ви разработчици ще вървят по-гладко. Git проектът има добре форматирани къмит съобщения — пробвайте да пуснете git log --no-merges в хранилището и ще видите колко добре форматирана е историята на проекта.

Note
Правете каквото казваме, а не каквото правим ние.

Много от примерите в тази книга съвсем не съдържат добре форматирани къмит съобщения; ние използваме -m параметъра към git commit за по-кратко.

Накратко, следвайте правилото за добра практика, а не гледайте какво сме направили ние.

Малък частен екип

Най-простата схема, на която може да попаднете е частен проект с един или двама разработчици. “Частен,” в този смисъл, означава със затворен код — недостъпен за външния свят. Вие всички имате достъп за писане до хранилището.

В такава среда, можете да следвате работен процес подобен на този, който бихте ползвали в Subversion или друга централизирана система. Може все още да използвате предимствата на неща като офлайн къмитване и съвсем прости разклонявания и сливания, но работният процес може да е много подобен; основната разлика е, че сливанията се случват от страна на клиента, вместо на сървъра по време на къмита. Нека видим как би могло да изглежда това, когато двама разработчика започнат съвместна работа със споделено хранилище. Първият програмист, John, клонира хранилището, прави промяна и къмитва локално. (Протоколните съобщения са заменени с ... в тези примери за да ги съкратим.)

# John's Machine
$ git clone john@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim lib/simplegit.rb
$ git commit -am 'remove invalid default value'
[master 738ee87] remove invalid default value
 1 files changed, 1 insertions(+), 1 deletions(-)

Вторият разработчик, Jessica, прави същото нещо — клонира хранилището и къмитва промяна:

# Jessica's Machine
$ git clone jessica@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim TODO
$ git commit -am 'add reset task'
[master fbff5bc] add reset task
 1 files changed, 1 insertions(+), 0 deletions(-)

Сега, Jessica публикува работата си на сървъра и това работи безпроблемно:

# Jessica's Machine
$ git push origin master
...
To jessica@githost:simplegit.git
   1edee6b..fbff5bc  master -> master

Последният ред от изхода отгоре показва полезно съобщение за резултата от push операцията. Основният формат е <oldref>..<newref> fromref -> toref, където oldref означава референция към предишния къмит, newref - към текущия, fromref е името на локалния клон, който се изпраща, и toref - на отдалечения, който се обновява. Ще виждате подобен изход по-натам в дискусиите, така че да имате представа какво означава това ще е полезно в осмислянето на различните статуси на хранилищата.

Повече подробности има в документацията на командата git-push.

Продължаваме с този пример. Скоро след това, John прави някакви промени, къмитва ги в локалното си хранилище и се опитва да ги изпрати в същия сървър:

# John's Machine
$ git push origin master
To john@githost:simplegit.git
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to 'john@githost:simplegit.git'

В този случай, опитът му завършва с неуспех, защото Jessica е изпратила нейните промени по-рано. Това е особено важно да се разбере, ако сте ползвали Subversion, защото вероятно забелязвате, че двамата разработчици не са редактирали един и същи файл. Въпреки че Subversion ще направи автоматично сливане на сървъра в случаи като този (променени различни файлове), при Git вие трябва първо да слеете къмитите локално. С други думи, John трябва първо да изтегли upstream промените на Jessica, да ги слее в локалното си хранилище и едва след това ще може да изпраща към сървъра.

Като първа стъпка, John изтегля работата на Jessica (командата отдолу само изтегля последната работа на Jessica, не я слива автоматично):

$ git fetch origin
...
From john@githost:simplegit
 + 049d078...fbff5bc master     -> origin/master

В този момент, локалното хранилище на John изглежда подобно на това:

Разклонена история на John.
Figure 58. Разклонена история на John.

Следва командата за сливане в локалното хранилище:

$ git merge origin/master
Merge made by the 'recursive' strategy.
 TODO |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

След като това локално сливане мине гладко, обновената история на John ще изглежда така:

Хранилището на John след сливането на `origin/master`.
Figure 59. Хранилището на John след сливането на origin/master.

В този момент, John може да поиска да провери дали новия код не засяга по някакъв начин неговата работа и след като всичко е нормално, може да изпрати всичко към сървъра:

$ git push origin master
...
To john@githost:simplegit.git
   fbff5bc..72bbc59  master -> master

В края, историята на къмитите на John ще изглежда така:

Историята на John след изпращане към `origin` сървъра.
Figure 60. Историята на John след изпращане към origin сървъра.

Междувременно, Jessica създава нов topic клон наречен issue54 и прави три къмита в него. Тя все още не е издърпала промените на John, така че историята ѝ изглежда така:

Topic клонът на Jessica.
Figure 61. Topic клонът на Jessica.

Внезапно Jessica научава, че John е публикувал някакви нови промени и иска да ги погледне, така че може да издърпа от сървъра:

# Jessica's Machine
$ git fetch origin
...
From jessica@githost:simplegit
   fbff5bc..72bbc59  master     -> origin/master

Това ще изтегли промените на John и историята на Jessica изглежда така:

Историята на Jessica след изтегляне на промените на John
Figure 62. Историята на Jessica след изтегляне на промените на John.

Jessica решава, че нейният topic клон вече е готов, но иска да знае коя част от работата на John трябва да слее със своята, така че да може да публикува. За целта тя изпълнява командата git log:

$ git log --no-merges issue54..origin/master
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 16:01:27 2009 -0700

   remove invalid default value

Синтаксисът issue54..origin/master е филтър, който указва на Git да покаже само тези къмити, които са налични във втория клон (в случая origin/master), но липсват в първия (issue54). Ще разгледаме в подробности този синтаксис в Обхвати от къмити.

От горния изход можем да видим, че съществува един къмит направен от John, който Jessica не е сляла в локалното си копие. Ако тя слее origin/master, това ще е единственият къмит, който би променил локалната ѝ работа.

Сега тя може да слее своя topic клон в master клона си, да слее работата на John (origin/master) пак в него и накрая да публикува всички промени към сървъра.

Първо (след като е къмитнала всичко в issue54 клона), Jessica превключва обратно към master клона си:

$ git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.

Jessica може да слее първо origin/master или issue54 — и двата са upstream, така че редът е без значение. Крайният snapshot трябва да е идентичен, само историята ще се различава. Тя решава да слее първо issue54 клона:

$ git merge issue54
Updating fbff5bc..4af4298
Fast forward
 README           |    1 +
 lib/simplegit.rb |    6 +++++-
 2 files changed, 6 insertions(+), 1 deletions(-)

Не възникват проблеми, това е просто fast-forward сливане. Jessica сега завършва процеса по локално сливане като вмъква и работата на John от клона orgin/master:

$ git merge origin/master
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

Всичко минава чисто и историята на Jessica сега изглежда така:

Историята на Jessica след сливане на промените от John.
Figure 63. Историята на Jessica след сливане на промените от John.

Сега origin/master е достъпен от master клона на Jessica, така че тя може успешно да публикува промените (приемаме, че междувременно John не е изпращал нови такива):

$ git push origin master
...
To jessica@githost:simplegit.git
   72bbc59..8059c15  master -> master

Сега всеки разработчик е къмитнал по няколко пъти и успешно е слял работата на колегата си.

Историята на Jessica след изпращането на всички промени към сървъра.
Figure 64. Историята на Jessica след изпращането на всички промени към сървъра.

Това е един от най-простите процеси на работа.

Вие работите известно време (обикновено в topic клон) и сливате работата си в master клона, когато е свършена. Когато желаете да споделите тази работа, вие изтегляте и сливате вашия master с origin/master, ако той е променен, накрая публикувате master клона си в сървъра. Общата последователност изглежда подобно:

Обичайна последователност от събития при прост работен процес с няколко разработчици в Git.
Figure 65. Обичайна последователност от събития при прост работен процес с няколко разработчици в Git.

Работа в управляван екип

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

Нека приемем, че John и Jessica работят по функционалността “feature A”, докато Jessica и друг програмист, Josie, работят по друга опция, “feature B”. В този случай, компанията използва работен процес от тип integration-manager, при който работата на индивидуалните групи се интегрира само от определени хора и master клонът на главното хранилище може да се обновява само от тези хора. В сценарий като този, цялата работа се върши в екипни клонове, които интеграторите събират по-късно.

Нека проследим работата на Jessica, защото тя работи по две неща, в паралел с двама различни разработчици. Допускаме, че тя вече има клонирано своето хранилище и решава да започне работа първо по featureA. Тя създава нов клон и работи по него тук:

# Jessica's Machine
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ vim lib/simplegit.rb
$ git commit -am 'add limit to log function'
[featureA 3300904] add limit to log function
 1 files changed, 1 insertions(+), 1 deletions(-)

В този момент, тя трябва да сподели работата си с John, така че изпраща къмитите от клона featureA към сървъра. Тя обаче няма push достъп до master клона, само интеграторите имат такъв — ето защо трябва да публикува в друг клон, за да работи съвместно с John:

$ git push -u origin featureA
...
To jessica@githost:simplegit.git
 * [new branch]      featureA -> featureA

Jessica изпраща на John съобщение за това, че е изпратила своята работа в клон наречен feature A и сега той може да го погледне. Докато чака за мнението на John, Jessica решава да започне работа по feature B с Josie. За да започне, тя стартира нов feature клон, базирайки го на master клона от сървъра:

# Jessica's Machine
$ git fetch origin
$ git checkout -b featureB origin/master
Switched to a new branch 'featureB'

Сега тя прави няколко къмита в клона featureB:

$ vim lib/simplegit.rb
$ git commit -am 'made the ls-tree function recursive'
[featureB e5b0fdc] made the ls-tree function recursive
 1 files changed, 1 insertions(+), 1 deletions(-)
$ vim lib/simplegit.rb
$ git commit -am 'add ls-files'
[featureB 8512791] add ls-files
 1 files changed, 5 insertions(+), 0 deletions(-)

Хранилището на Jessica сега изглежда така:

Първоначалните къмити на Jessica.
Figure 66. Първоначалните къмити на Jessica.

Готова е да изпрати своята работа, но в този момент получава имейл от Josie, в който се съобщава, че на сървъра вече има създаден клон с някаква предварително свършена работа по “featureB”. Този клон се казва featureBee. Jessica сега трябва първо да слее тези промени със своите собствени преди да може да изпрати към сървъра. Тя изтегля промените на Josie с git fetch:

$ git fetch origin
...
From jessica@githost:simplegit
 * [new branch]      featureBee -> origin/featureBee

Приемайки, че локално тя е все още в клона featureB, тя може да слее работата на Josie с git merge:

$ git merge origin/featureBee
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.
 lib/simplegit.rb |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

В този момент, тя иска да изпрати цялата си слята работа от “featureB” обратно към сървъра, но не иска да го направи просто изпращайки своя собствен featureB клон. Вместо това, понеже Josie вече е направил upstream featureBee клон, Jessica ще иска да изпраща промените си именно към този клон, затова тя прави така:

$ git push -u origin featureB:featureBee
...
To jessica@githost:simplegit.git
   fba9af8..cd685d1  featureB -> featureBee

Това се нарича refspec, указване на референция. Вижте Refspec спецификации за повече детайли относно Git refspecs и различните възможности, които ви се предоставят с тях. Отбележете също -u флага; това е съкращение за --set-upstream, който аргумент конфигурира клоновете за лесно дърпане и изпращане.

Внезапно Jessica получава имейл от John, който ѝ казва, че е публикувал някои промени по featureA клона, по който си сътрудничат и моли Jessica да ги погледне. Отново, Jessica изпълнява git fetch за да изтегли цялото ново съдържание от сървъра, включително последните промени на John:

$ git fetch origin
...
From jessica@githost:simplegit
   3300904..aad881d  featureA   -> origin/featureA

Jessica може да покаже историята на промените на John сравнявайки съдържанието на новоизвлечения featureA клон с локалното копие на същия такъв:

$ git log featureA..origin/featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 19:57:33 2009 -0700

    changed log output to 30 from 25

Ако Jessica харесва това, което вижда, тя може да слее промените от John в локалния си featureA клон:

$ git checkout featureA
Switched to branch 'featureA'
$ git merge origin/featureA
Updating 3300904..aad881d
Fast forward
 lib/simplegit.rb |   10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)

Накрая, Jessica може да желае да направи няколко малки корекции към цялото това слято съдържание, така че е свободна да ги реализира, да ги къмитне в локалния featureA клон, и да публикува финалния резултат на сървъра.

$ git commit -am 'small tweak'
[featureA 774b3ed] small tweak
 1 files changed, 1 insertions(+), 1 deletions(-)
$ git push
...
To jessica@githost:simplegit.git
   3300904..774b3ed  featureA -> featureA

Сега историята на къмитите на Jessica ще изглежда по подобен начин:

Историята на Jessica след къмитите.
Figure 67. Историята на Jessica след къмитите.

В даден момент, Jessica, Josie и John информират интеграторите, че featureA и featureBee клоновете на сървъра са готови за интегриране. След като интеграторите слеят тези клонове в основния проект, едно изтегляне ще предостави новия merge къмит, променяйки историята така:

Историята на Jessica след сливането на двата ѝ topic клона.
Figure 68. Историята на Jessica след сливането на двата ѝ topic клона.

Много екипи преминават към Git поради възможностите за паралелна работа на множество тимове и сливане на различните работни нишки по-късно в процеса. Възможността малки подгрупи от един екип да работят съвместно чрез отдалечени клонове без да е необходимо да въвличат в това целия екип или да възпрепятстват работата на другите, е огромно преимущество с Git. Последователността на работния процес, който описахме, би могла да се илюстрира така:

Последователност на действията в managed-team работен процес.
Figure 69. Последователност на действията в managed-team работен процес.

Клониран публичен проект

Допринасянето към публични проекти е малко по-различно. Понеже нямате права директно да обновявате клоновете на един такъв проект, по някакъв начин трябва да изпратите работата си до хората, които го поддържат. Този първи пример описва как се сътрудничи чрез клониране на Git хостове, които поддържат easy forking. Много от публичните хостинг платформи (вкл. GitHub, BitBucket, repo.or.cz, и други), както и много от поддържащите дадени проекти, очакват именно такъв подход за външна помощ. Следващата секция се занимава с проекти, които предпочитат да приемат предложените пачове чрез имейл.

Първо, вероятно ще искате да клонирате главното хранилище, да създадете topic клон за пача или серията пачове, които планирате да предложите, и да работите в него. Последователността изглежда така:

$ git clone <url>
$ cd project
$ git checkout -b featureA
  ... work ...
$ git commit
  ... work ...
$ git commit
Note

Може да искате да използвате rebase -i за да съберете работата си в единичен къмит, или пък да реорганизирате нещата в множество къмити, така че мениджърът на проекта по-лесно да я разгледа — вижте Манипулация на историята за повече информация за интерактивното пребазиране.

Когато работата ви по клона е завършена и сте готови да я предложите на поддържащите проекта, отидете обратно в неговата страница и използвайте бутона “Fork”, така че да си направите свое собствено копие от проекта в сайта, с права за писане в него. След това трябва да добавите URL-а на копието като нова отдалечена референция за локалното ви хранилище, за този пример - нека да я наречем myfork:

$ git remote add myfork <url>

След това трябва да публикувате локалната си работа в това отдалечено хранилище. Най-лесно е да публикувате topic клона, по който работите, вместо първо са го сливате в master клона и да публикувате него. Причината за това е, че ако работата не бъде приета или е cherry-picked, няма да има нужда да превъртате обратно вашия master клон (cherry-pick операцията на Git се разглежда в повече подробности в Rebasing и Cherry-Picking работни процеси). Ако поддържащите проекта слеят, пребазират, или cherry-pick-нат вашата работа, в крайна сметка ще я получите обратно чрез издърпване от тяхното хранилище.

Във всеки случай, можете да публикувате работата си чрез:

$ git push -u myfork featureA

След като работата ви е била публикувана във вашето клонирано онлайн хранилище, ще трябва да уведомите поддържащите оригиналния проект, че имате промени, които бихте искали да интегрират. Това често се нарича pull request и обикновено такава заявка се генерира или директно през уеб сайта — GitHub има собствен “Pull Request” механизъм, който ще видим в GitHub — или пък с командата git request-pull чийто изход трябва да изпратите по имейл на мениджъра на проекта ръчно.

Командата git request-pull взема за параметри базовия клон, в който искате промените ви да бъдат интегрирани и адреса на Git хранилището, от което тези промени да бъдат изтеглени, след което изготвя списък с всички промени, които искате да бъдат изтеглени. Например, ако Jessica иска да изпрати на John pull request и е направила два къмита в topic клона, който току що е публикувала, тя може да изпълни следното:

$ git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:
Jessica Smith (1):
        added a new function

are available in the git repository at:

  git://githost/simplegit.git featureA

Jessica Smith (2):
      add limit to log function
      change log output to 30 from 25

 lib/simplegit.rb |   10 +++++++++-
 1 files changed, 9 insertions(+), 1 deletions(-)

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

В проект, който не поддържате, обикновено е по-лесно да имате клон като master, който винаги следи origin/master ` и да вършите работата си в topic клонове, които лесно можете да премахнете, в случай че бъдат отхвърлени. Изолирането на работата в topic клонове също така прави по-лесно пребазирането на вашата работа, ако "върха" на главното хранилище се премести междувременно и къмитите ви вече не се прилагат чисто. Например, ако искате да изпратите втора промяна в проекта, не продължавайте да работите в topic клона, който вече сте изпратили -- започнете от `master клона на главното хранилище:

$ git checkout -b featureB origin/master
  ... work ...
$ git commit
$ git push myfork featureB
$ git request-pull origin/master myfork
  ... email generated request pull to maintainer ...
$ git fetch origin

Сега, всяка от промените ви се съдържа в силоз — подобно на опашка от пачове — които можете да пренапишете, пребазирате и променяте без различните теми да се преплитат или да зависят една от друга:

Първоначална история на къмитите с `featureB`.
Figure 70. Първоначална история на къмитите с featureB.

Да кажем, че мениджърът на проекта е интегрирал множество други пачове и е опитал вашия първи клон, който обаче вече не се слива чисто. В този случай, можете да пробвате да пребазирате този клон върху origin/master, да оправите конфликтите и да изпратите повторно промените си:

$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA

Това ще промени историята ви така:

Историята след работа по `featureA`.
Figure 71. Историята след работа по featureA.

Понеже пребазирахте клона, трябва да подадете параметъра -f към push командата за да можете да замените featureA клона на сървъра с къмит, който не е негов наследник. Като алтернатива, можете да публикувате тази нова работа към различен клон в сървъра (например featureAv2).

Нека погледнем още един възможен сценарий: поддържащият проекта е погледнал работата във втория ви клон и харесва идеята ви, но би желал да промените малко имплементацията. Вие също ще се възползвате от тази възможност за да преместите работата си така, че да е базирана на текущия master клон на проекта. Стартирате нов клон базиран на текущия origin/master, премествате featureB промените в него, решавате евентуалните конфликти, променяте имплементацията и го публикувате като нов клон:

$ git checkout -b featureBv2 origin/master
$ git merge --squash featureB
  ... change implementation ...
$ git commit
$ git push myfork featureBv2

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

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

Историята след работата по `featureBv2`.
Figure 72. Историята след работата по featureBv2.

Публичен проект с комуникация по имейл

Много проекти имат установени процедури за приемане на пачове — ще трябва да проверите специфичните изисквания според вашия случай. Понеже все още съществуват множество по-стари, големи по мащаб проекти, които приемат пачовете през мейлинг лист за разработчици, ще дадем и един пример за такъв случай.

Работният процес е подобен на предишния — вие създавате topic клонове за всяка серия от пачове, по която работите. Разликата е в това как ги изпращате към проекта. Вместо да клонирате проекта и да публикувате във вашата версия, в която имате права за писане, вие генерирате имейл версии на всяка серия къмити и ги изпращате към мейлинг лист:

$ git checkout -b topicA
  ... work ...
$ git commit
  ... work ...
$ git commit

Сега имате два къмита, които искате да пратите. Използвате git format-patch командата, за да генерирате mbox-форматирани файлове, които можете да пратите към списъка — тя превръща всеки къмит в имейл съобщение със subject отговарящ на първия ред от къмит съобщението и останалата част от съобщението плюс пача - като тяло на имейла. Хубавото на това е, че прилагането на пач от имейл съобщение генерирано с тази команда запазва коректно цялата информация за къмита.

$ git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch

Командата format-patch отпечатва имената на пач-файловете, които създава. Параметърът -M казва на Git да търси за преименувания. Файловете изглеждат така:

$ cat 0001-add-limit-to-log-function.patch
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

---
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 76f47bc..f9815f1 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -14,7 +14,7 @@ class SimpleGit
   end

   def log(treeish = 'master')
-    command("git log #{treeish}")
+    command("git log -n 20 #{treeish}")
   end

   def ls_tree(treeish = 'master')
--
2.1.0

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

За да изпратите това към листата, можете или да копирате файла в имейл програмата си или да използвате програма за изпращане на поща от команден ред. Копирането на текст често предизвиква проблеми с форматирането, особено с "по-умните" клиенти, които не запазват новите редове и празните символи коректно. За щастие, Git предоставя инструмент за изпращане на правилно форматирани пачове през IMAP, което може да е по-лесно за вас. Ще покажем как да изпращаме пач през Gmail, тъй като е много популярна услуга, но можете да намерите детайлни инструкции за използването на много други имейл клиенти в края на гореспоменатия файл Documentation/SubmittingPatches в сорс кода на Git.

Първо, трябва да подготвите imap секцията във вашия ~/.gitconfig файл. Можете да настроите всяка стойност отделно чрез серия от git config команди или пък да ги въведете ръчно, но независимо от подхода, конфигурационният файл ще изглежда по подобен начин:

[imap]
  folder = "[Gmail]/Drafts"
  host = imaps://imap.gmail.com
  user = user@gmail.com
  pass = YX]8g76G_2^sFbd
  port = 993
  sslverify = false

Ако IMAP сървърът ви не използва SSL, то последните два реда вероятно няма да са нужни и стойността за хоста ще започва с imap:// вместо с imaps://. Когато това е готово, можете да използвате командата git imap-send за да поставите пачовете в Drafts папката на указания IMAP сървър:

$ cat *.patch |git imap-send
Resolving imap.gmail.com... ok
Connecting to [74.125.142.109]:993... ok
Logging in...
sending 2 messages
100% (2/2) done

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

Можете да изпращате и през SMTP сървър. Както и преди, ще трябва да отразите промените в ~/.gitconfig, в секцията му sendemail, ръчно или с git config команди:

[sendemail]
  smtpencryption = tls
  smtpserver = smtp.gmail.com
  smtpuser = user@gmail.com
  smtpserverport = 587

След това, използвайте командата git send-email за да изпратите пачовете си:

$ git send-email *.patch
0001-added-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch
Who should the emails appear to be from? [Jessica Smith <jessica@example.com>]
Emails will be sent from: Jessica Smith <jessica@example.com>
Who should the emails be sent to? jessica@example.com
Message-ID to be used as In-Reply-To for the first email? y

След това Git показва множество лог информация, която изглежда по подобен начин за всеки от пачовете:

(mbox) Adding cc: Jessica Smith <jessica@example.com> from
  \line 'From: Jessica Smith <jessica@example.com>'
OK. Log says:
Sendmail: /usr/sbin/sendmail -i jessica@example.com
From: Jessica Smith <jessica@example.com>
To: jessica@example.com
Subject: [PATCH 1/2] added limit to log function
Date: Sat, 30 May 2009 13:29:15 -0700
Message-Id: <1243715356-61726-1-git-send-email-jessica@example.com>
X-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty
In-Reply-To: <y>
References: <y>

Result: OK

Обобщение

Тази секция разгледа няколко възможни работни последователности за различните Git проекти, които бихте могли да срещнете и посочи няколко нови инструмента, които да ви помогнат да управлявате процеса на работа. Следва да погледнем от обратната страна на монетата: поддържането на Git проект. Ще научите как да изпълнявате ролите на benevolent dictator или integration manager.