-
1. Aan die slag
- 1.1 Oor Weergawebeheer
- 1.2 Wat is Git?
- 1.3 Die Opdragreël
- 1.4 Git Installeer
- 1.5 Git klaarmaak vir eerste gebruik
- 1.6 Hulp Verkry
- 1.7 Opsomming
-
2. Git Basics
-
3. Git Branching
-
4. Git on the Server
- 4.1 Die Protokolle (The Protocols)
- 4.2 Git op 'n Bediener kry (Getting Git on a Server)
- 4.3 Jou Publieke SSH-sleutel Genereer (Generating Your SSH Public Key)
- 4.4 Die Bediener Opstel (Setting Up the Server)
- 4.5 Git Daemon
- 4.6 Slim HTTP (Smart HTTP)
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Derdeparty-gasheuroplossings (Third-Party Hosting Solutions)
- 4.10 Summary
-
5. Distributed Git
-
6. GitHub
-
7. Git Tools
- 7.1 Hersieningseleksie (Revision Selection)
- 7.2 Interaktiewe Voorbereiding (Interactive Staging)
- 7.3 Bêre en Skoonmaak (Stashing and Cleaning)
- 7.4 Ondertekening van Jou Werk (Signing Your Work)
- 7.5 Soek (Searching)
- 7.6 Herskryf van Geskiedenis (Rewriting History)
- 7.7 Reset Ontmystifiseer (Reset Demystified)
- 7.8 Gevorderde Saamsmelting (Advanced Merging)
- 7.9 Rerere
- 7.10 Ontfouting met Git (Debugging with Git)
- 7.11 Submodules
- 7.12 Bundeling (Bundling)
- 7.13 Vervang (Replace)
- 7.14 Die Stoor van Aanmeldbewyse (Credential Storage)
- 7.15 Summary
-
8. Customizing Git
-
9. Git and Other Systems
- 9.1 Git as a Client
- 9.2 Migrating to Git
- 9.3 Summary
-
10. Git Internals
8.4 Customizing Git - 'n Voorbeeld van 'n Git-Afgedwonge Beleid (An Example Git-Enforced Policy)
'n Voorbeeld van 'n Git-Afgedwonge Beleid (An Example Git-Enforced Policy)
In hierdie afdeling sal jy gebruik wat jy geleerd het om 'n Git-werkvloei te vestig wat kyk vir 'n pasgemaakte vasleggingsboodskapformaat, en slegs sekere gebruikers toelaat om sekere subgidse in 'n projek te wysig. Jy sal kliëntskrippe bou wat die ontwikkelaar laat weet of hul push geweiger gaan word, en bedienerskrippe wat werklik die beleid afdwing.
Die skrippe wat ons sal wys, is in Ruby geskryf; gedeeltelik as gevolg van ons eie intelektuele traagheid, maar ook omdat Ruby maklik is om te lees, selfs al kan jy dit nie noodwendig skryf nie. Enige taal sal egter werk – al die voorbeeldhaakskrippe wat saam met Git versprei word, is in óf Perl óf Bash, so jy kan ook talle voorbeelde van hake in daardie tale sien deur na die voorbeelde te kyk.
Bedienerkant-haak (Server-Side Hook)
Al die bedienerkantwerk sal in die update lêer in jou hooks gids ingaan.
Die update haak loop een keer per tak wat gepush word en neem drie argumente:
-
Die naam van die verwysing waarna gepusht word
-
Die ou hersiening (revision) waar daardie tak was
-
Die nuwe hersiening wat gepusht word
Jy het ook toegang tot die gebruiker wat die push doen as die push oor SSH uitgevoer word.
As jy almal toegelaat het om met 'n enkele gebruiker (soos git) via publieke-sleutel-verifikasie te verbind, moet jy dalk daardie gebruiker 'n dop-toedraaier (shell wrapper) gee wat bepaal watter gebruiker verbind op grond van die publieke sleutel, en 'n omgewingsveranderlike dienooreenkomstig stel.
Hier sal ons aanneem dat die verbindende gebruiker in die $USER omgewingsveranderlike is, sodat jou update-skrip begin deur al die inligting wat jy nodig het, te versamel:
#!/usr/bin/env ruby
$refname = ARGV[0]
$oldrev = ARGV[1]
$newrev = ARGV[2]
$user = ENV['USER']
puts "Enforcing Policies..."
puts "(#{$refname}) (#{$oldrev[0,6]}) (#{$newrev[0,6]})"
Ja, dit is globale veranderlikes. Moenie oordeel nie – dit is makliker om dit op hierdie manier te demonstreer.
Afdwing van 'n Spesifieke Vasleggingsboodskapformaat (Enforcing a Specific Commit-Message Format)
Jou eerste uitdaging is om af te dwing dat elke vasleggingsboodskap by 'n bepaalde formaat hou. Net om 'n teiken te hê, aanvaar dat elke boodskap 'n string moet insluit wat so lyk as “ref: 1234” omdat jy wil hê elke vaslegging moet skakel na 'n werkitem in jou kaartjiesisteem (ticketing system). Jy moet kyk na elke vaslegging wat opgestoot word, kyk of daardie string in die vasleggingsboodskap is, en, indien die string van enige van die vasleggings afwesig is, nie-nul uitstaan (exit non-zero) sodat die push geweiger word.
Jy kan 'n lys kry van die SHA-1 waardes van al die vasleggings wat gepusht word deur die $newrev en $oldrev waardes te neem en dit aan 'n Git-loodgietersopdrag genaamd git rev-list te gee.
Dit is basies die git log opdrag, maar by verstek druk dit slegs die SHA-1 waardes uit en geen ander inligting nie.
Dus, om 'n lys te kry van al die vaslegging SHA-1’s wat ingestel is tussen een vaslegging SHA-1 en 'n ander, kan jy iets soos dit uitvoer:
$ git rev-list 538c33..d14fc7
d14fc7c847ab946ec39590d87783c69b031bdfb7
9f585da4401b0a3999e84113824d15245c13f0be
234071a1be950e2a8d078e6141f5cd20c1e61ad3
dfa04c9ef3d5197182f13fb5b9b1fb7717d2222a
17716ec0f1ff5c77eff40b7fe912f9f6cfd0e475
Jy kan daardie afvoer neem, deur elkeen van daardie vaslegging SHA-1’s loop, die boodskap daarvoor gryp, en daardie boodskap toets teen 'n gereelde uitdrukking (regular expression) wat soek na 'n patroon.
Jy moet uitvind hoe om die vasleggingsboodskap van elkeen van hierdie vasleggings te kry om te toets.
Om die rou vasleggingsdata te kry, kan jy 'n ander loodgietersopdrag genaamd git cat-file gebruik.
Ons sal in detail deur al hierdie loodgietersopdragte gaan in Git Internals; maar vir eers is dit wat daardie opdrag vir jou gee:
$ git cat-file commit ca82a6
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
author Scott Chacon <schacon@gmail.com> 1205815931 -0700
committer Scott Chacon <schacon@gmail.com> 1240030591 -0700
Change the version number
'n Eenvoudige manier om die vasleggingsboodskap van 'n vaslegging te kry wanneer jy die SHA-1 waarde het, is om na die eerste leë reël te gaan en alles daarna te neem.
Jy kan dit doen met die sed opdrag op Unix-stelsels:
$ git cat-file commit ca82a6 | sed '1,/^$/d'
Change the version number
Jy kan daardie beswering (incantation) gebruik om die vasleggingsboodskap te gryp van elke vaslegging wat probeer word om gepush te word en uit te staan as jy iets sien wat nie ooreenstem nie. Om die skrip te verlaat en die push te weiger, voer 'n nie-nul uitstaan uit. Die hele metode lyk soos volg:
$regex = /\[ref: (\d+)\]/
# enforced custom commit message format
def check_message_format
missed_revs = `git rev-list #{$oldrev}..#{$newrev}`.split("\n")
missed_revs.each do |rev|
message = `git cat-file commit #{rev} | sed '1,/^$/d'`
if !$regex.match(message)
puts "[POLICY] Your message is not formatted correctly"
exit 1
end
end
end
check_message_format
Om dit in jou update skrip te plaas, sal opdaterings verwerp wat vasleggings bevat met boodskappe wat nie by jou reël hou nie.
Afdwing van 'n Gebruikergebaseerde ACL-stelsel (Enforcing a User-Based ACL System)
Veronderstel jy wil 'n meganisme byvoeg wat 'n toegangsbeheerelys (Access Control List - ACL) gebruik wat spesifiseer watter gebruikers toegelaat word om veranderings te push na watter dele van jou projekte.
Sommige mense het volle toegang, en ander kan slegs veranderings push na sekere subgidse of spesifieke lêers.
Om dit af te dwing, sal jy daardie reëls skryf na 'n lêer genaamd acl wat in jou kaal (bare) Git-bewaarplek op die bediener leef.
Jy sal hê dat die update haak na daardie reëls kyk, sien watter lêers ingestel word vir al die vasleggings wat gepusht word, en bepaal of die gebruiker wat die push doen toegang het om al daardie lêers op te dateer.
Die eerste ding wat jy sal doen is om jou ACL te skryf.
Hier sal jy 'n formaat gebruik wat baie lyk soos die CVS ACL-meganisme: dit gebruik 'n reeks reëls, waar die eerste veld avail of unavail is, die volgende veld 'n kommaskeiding-lys is van die gebruikers op wie die regel van toepassing is, en die laaste veld die pad is waarop die regel van toepassing is (leeg beteken oop toegang).
Al hierdie velde word deur 'n pyp (|) karakter geskei.
In hierdie geval het jy 'n paar administrateurs, 'n paar dokumentasieskrywers met toegang tot die doc gids, en een ontwikkelaar wat slegs toegang tot die lib en tests gidse het, en jou ACL-lêer lyk soos dit:
avail|nickh,pjhyett,defunkt,tpw
avail|usinclair,cdickens,ebronte|doc
avail|schacon|lib
avail|schacon|tests
Jy begin deur hierdie data in 'n struktuur in te lees wat jy kan gebruik.
In hierdie geval, om die voorbeeld eenvoudig te hou, sal jy slegs die avail direktiewe afdwing.
Hier is 'n metode wat vir jou 'n assosiatiewe skikking (associative array) gee waar die sleutel die gebruikersnaam is en die waarde 'n skikking van paaie is waartoe die gebruiker skryftoegang het:
def get_acl_access_data(acl_file)
# read in ACL data
acl_file = File.read(acl_file).split("\n").reject { |line| line == '' }
access = {}
acl_file.each do |line|
avail, users, path = line.split('|')
next unless avail == 'avail'
users.split(',').each do |user|
access[user] ||= []
access[user] << path
end
end
access
end
Op die ACL-lêer waarna jy vroeër gekyk het, gee hierdie get_acl_access_data metode 'n datastruktuur terug wat so lyk:
{"defunkt"=>[nil],
"tpw"=>[nil],
"nickh"=>[nil],
"pjhyett"=>[nil],
"schacon"=>["lib", "tests"],
"cdickens"=>["doc"],
"usinclair"=>["doc"],
"ebronte"=>["doc"]}
Noudat jy die toestemmings uitsorteer het, moet jy bepaal watter paaie die vasleggings wat gepusht word gewysig het, sodat jy seker kan maak die gebruiker wat push het toegang tot almal van hulle.
Jy kan baie maklik sien watter lêers in 'n enkelvoudige vaslegging gewysig is met die --name-only opsie vir die git log opdrag (kortliks genoem in Git Basics):
$ git log -1 --name-only --pretty=format:'' 9f585d
README
lib/test.rb
As jy die ACL-struktuur gebruik wat van die get_acl_access_data metode teruggegee word en dit kontroleer teen die gelysde lêers in elkeen van die vasleggings, kan jy bepaal of die gebruiker toegang het om al hul vasleggings te push:
# only allows certain users to modify certain subdirectories in a project
def check_directory_perms
access = get_acl_access_data('acl')
# see if anyone is trying to push something they can't
new_commits = `git rev-list #{$oldrev}..#{$newrev}`.split("\n")
new_commits.each do |rev|
files_modified = `git log -1 --name-only --pretty=format:'' #{rev}`.split("\n")
files_modified.each do |path|
next if path.size == 0
has_file_access = false
access[$user].each do |access_path|
if !access_path # user has access to everything
|| (path.start_with? access_path) # access to this path
has_file_access = true
end
end
if !has_file_access
puts "[POLICY] You do not have access to push to #{path}"
exit 1
end
end
end
end
check_directory_perms
Jy kry 'n lys van nuwe vasleggings wat na jou bediener gepush word met git rev-list.
Dan, vir elkeen van daardie vasleggings, vind jy watter lêers gewysig is en maak seker die gebruiker wat push het toegang tot al die paaie wat gewysig word.
Nou kan jou gebruikers nie enige vasleggings push met sleg gevormde boodskappe of met gewysigde lêers buite hul aangewese paaie nie.
Toetsing Dit Uit (Testing It Out)
As jy chmod u+x .git/hooks/update uitvoer, wat die lêer is waarin jy al hierdie kode moes geplaas het, en dan probeer om 'n vaslegging te push met 'n nie-nakomende boodskap, kry jy iets soos dit:
$ git push -f origin master
Counting objects: 5, done.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 323 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
Enforcing Policies...
(refs/heads/master) (8338c5) (c5b616)
[POLICY] Your message is not formatted correctly
error: hooks/update exited with error code 1
error: hook declined to update refs/heads/master
To git@gitserver:project.git
! [remote rejected] master -> master (hook declined)
error: failed to push some refs to 'git@gitserver:project.git'
Daar is 'n paar interessante dinge hier. Eerstens sien jy dit waar die haak begin loop.
Enforcing Policies...
(refs/heads/master) (fb8c72) (c56860)
Onthoud dat jy dit aan die allerbegin van jou update-skrip uitgevoer (gedruk) het.
Enigiets wat jou skrip na stdout eggo, sal na die kliënt oorgedra word.
Die volgende ding wat jy sal opmerk, is die foutboodskap.
[POLICY] Your message is not formatted correctly
error: hooks/update exited with error code 1
error: hook declined to update refs/heads/master
Die eerste reël is deur jou gedruk, die ander twee was Git wat jou vertel het dat die update-skrip nie-nul uitgestaan het en dit is wat jou push van die hand wys. Laastens het jy dit:
To git@gitserver:project.git
! [remote rejected] master -> master (hook declined)
error: failed to push some refs to 'git@gitserver:project.git'
Jy sal 'n remote-afgewese boodskap sien vir elke verwysing wat jou haak van die hand gewys het, en dit sê vir jou dat dit spesifiek van die hand gewys is as gevolg van 'n haakfaling.
Verder, as iemand probeer om 'n lêer te redigeer waartoe hulle nie toegang het nie en 'n vaslegging wat dit bevat te push, sal hulle iets soortgelyks sien.
Byvoorbeeld, as 'n dokumentasieskrywer probeer om 'n vaslegging te push wat iets in die lib gids wysig, sien hulle:
[POLICY] You do not have access to push to lib/test.rb
Van nou af, solank daardie update skrip daar en uitvoerbaar is, sal jou bewaarplek nooit 'n vasleggingsboodskap sonder jou patroon daarin hê nie, en jou gebruikers sal in 'n sandput (sandboxed) wees.
Kliëntkant-hake (Client-Side Hooks)
Die nadeel van hierdie benadering is die gekerm wat onvermydelik sal volg wanneer jou gebruikers se vasleggingspushes geweiger word. Om te sien dat hul sorgvuldig vervaardigde werk op die laaste oomblik geweiger word, kan uiterst frustrerend en verwarrend wees; en bowendien sal hulle hul geskiedenis moet redigeer om dit te korrigeer, wat nie altyd vir die flouhartiges is nie.
Die antwoord op hierdie dilemma is om 'n paar kliëntkant-hake te verskaf wat gebruikers kan uitvoer om hulle te ken te gee wanneer hulle iets doen wat die bediener waarskynlik sal weier.
Op daardie manier kan hulle enige probleme korrigeer voor vaslegging en voordat daardie kwessies moeiliker word om reg te maak.
Omdat hake nie saam met 'n kloon van 'n projek oorgedra word nie, moet jy hierdie skrippe op 'n ander manier versprei en dan jou gebruikers dit na hul .git/hooks gids laat kopieer en dit uitvoerbaar maak.
Jy kan hierdie hake binne die projek of in 'n aparte projek versprei, maar Git sal dit nie outomaties opstel nie.
Om te begin, moet jy jou vasleggingsboodskap net voor elke vaslegging opgeteken word, kontroleer sodat jy weet die bediener sal nie jou veranderings weier as gevolg van sleg geformatteerde vasleggingsboodskappe nie.
Om dit te doen, kan jy die commit-msg haak byvoeg.
As jy dit die boodskap laat lees uit die lêer wat as die eerste argument deurgegee word en dit vergelyk met die patroon, kan jy Git dwing om die vaslegging te staak as daar geen ooreenkoms is nie:
#!/usr/bin/env ruby
message_file = ARGV[0]
message = File.read(message_file)
$regex = /\[ref: (\d+)\]/
if !$regex.match(message)
puts "[POLICY] Your message is not formatted correctly"
exit 1
end
As daardie skrip in plek is (in .git/hooks/commit-msg) en uitvoerbaar is, en jy vaslê met 'n boodskap wat nie behoorlik geformatteer is nie, sien jy dit:
$ git commit -am 'Test'
[POLICY] Your message is not formatted correctly
Geen vaslegging is in daardie instansie voltooi nie. As jou boodskap egter die juiste patroon bevat, laat Git jou toe om vas te lê:
$ git commit -am 'Test [ref: 132]'
[master e05c914] Test [ref: 132]
1 file changed, 1 insertions(+), 0 deletions(-)
Vervolgens wil jy seker maak dat jy nie lêers wysig wat buite jou ACL-omvang is nie.
As jou projek se .git gids 'n kopie bevat van die ACL-lêer wat jy vroeër gebruik het, dan sal die volgende pre-commit skrip daardie beperkings vir jou afdwing:
#!/usr/bin/env ruby
$user = ENV['USER']
# [ insert acl_access_data method from above ]
# only allows certain users to modify certain subdirectories in a project
def check_directory_perms
access = get_acl_access_data('.git/acl')
files_modified = `git diff-index --cached --name-only HEAD`.split("\n")
files_modified.each do |path|
next if path.size == 0
has_file_access = false
access[$user].each do |access_path|
if !access_path || (path.index(access_path) == 0)
has_file_access = true
end
if !has_file_access
puts "[POLICY] You do not have access to push to #{path}"
exit 1
end
end
end
check_directory_perms
Hierdie is rofweg dieselfde skrip as die bedienerkant-deel, maar met twee belangrike verskille.
Eerstens is die ACL-lêer op 'n ander plek, omdat hierdie skrip vanaf jou werkgids loop, nie vanaf jou .git gids nie.
Jy moet die pad na die ACL-lêer hiervan verander:
access = get_acl_access_data('acl')
na dit toe:
access = get_acl_access_data('.git/acl')
Die ander belangrike verskil is die manier waarop jy 'n lys kry van die lêers wat verander is. Omdat die bedienerkant-metode na die log van vasleggings kyk, en op hierdie punt is die vaslegging nog nie opgeteken nie, moet jy jou lêerlys uit die voorbereidingsarea (staging area) in plaas daarvan kry. In plaas van:
files_modified = `git log -1 --name-only --pretty=format:'' #{ref}`
moet jy dit gebruik:
files_modified = `git diff-index --cached --name-only HEAD`
Maar dit is die enigste twee verskille – anders werk die skrip op dieselfde manier.
Een voorbehoud is dat dit verwag dat jy plaaslik sal loop as dieselfde gebruiker as wat jy as op die afgeleë masjien push.
As dit anders is, moet jy die $user veranderlike handmatig stel.
Nog 'n ding wat ons hier kan doen, is om seker te maak die gebruiker push nie nie-vinnig-voorwaartse verwysings (non-fast-forwarded references) nie. Om 'n verwysing te kry wat nie 'n vinnige-voorwaartse is nie, moet jy óf verby 'n vaslegging rebase wat jy reeds opgestoot het, of probeer om 'n ander plaaslike tak na dieselfde afgeleë tak op te stoot.
Vermoedelik is die bediener reeds gekonfigureer met receive.denyDeletes en receive.denyNonFastForwards om hierdie beleid af te dwing, sodat die enigste toevallige ding wat jy kan probeer vasvang, die rebase is van vasleggings wat reeds gepush is.
Hier is 'n voorbeeld pre-rebase skrip wat daarvoor kyk. Dit kry 'n lys van al die vasleggings wat jy op die punt staan om te herskryf en kyk of hulle in enige van jou afgeleë verwysings bestaan. As dit een sien wat bereikbaar is vanaf een van jou afgeleë verwysings, staak dit die rebase.
#!/usr/bin/env ruby
base_branch = ARGV[0]
if ARGV[1]
topic_branch = ARGV[1]
else
topic_branch = "HEAD"
end
target_shas = `git rev-list #{base_branch}..#{topic_branch}`.split("\n")
remote_refs = `git branch -r`.split("\n").map { |r| r.strip }
target_shas.each do |sha|
remote_refs.each do |remote_ref|
shas_pushed = `git rev-list ^#{sha}^@ refs/remotes/#{remote_ref}`
if shas_pushed.split("\n").include?(sha)
puts "[POLICY] Commit #{sha} has already been pushed to #{remote_ref}"
exit 1
end
end
end
Hierdie skrip gebruik 'n sintaksis wat nie in Hersieningseleksie (Revision Selection) gedek is nie. Jy kry 'n lys van vasleggings wat reeds opgestoot is deur dit uit te voer:
`git rev-list ^#{sha}^@ refs/remotes/#{remote_ref}`
Die SHA^@ sintaksis los op na al die ouers van daardie vaslegging.
Jy soek na enige vaslegging wat bereikbaar is vanaf die laaste vaslegging op die remote en wat nie bereikbaar is vanaf enige ouer van enige van die SHA-1’s wat jy probeer opstoot nie – wat beteken dit is 'n vinnige-voorwaartse.
Die belangrikste nadeel van hierdie benadering is dat dit baie stadig kan wees en dikwels onnodig is – as jy nie probeer om die push te forceer met -f nie, sal die bediener jou waarsku en nie die push aanvaar nie.
Dit is egter 'n interessante oefening en kan in teorie help om 'n rebase te vermy wat jy dalk later sal moet teruggaan en regmaak.