Chapters ▾ 2nd Edition

9.1 Git och andra system - Git som klient

Världen är inte perfekt. Ofta kan du inte omedelbart byta varje projekt du stöter på till Git. Ibland sitter du fast i ett projekt som använder ett annat VCS och önskar att det var Git. Vi ägnar den första delen av kapitlet åt att lära oss olika sätt att använda Git som klient när projektet du arbetar med ligger i ett annat system.

Vid något tillfälle vill du kanske konvertera ditt befintliga projekt till Git. Den andra delen av kapitlet beskriver hur du migrerar ditt projekt till Git från flera specifika system, samt en metod som fungerar om det inte finns något färdigt importverktyg.

Git som klient

Git ger utvecklare en så bra upplevelse att många har lärt sig att använda det på sin arbetsstation, även om resten av teamet använder ett annat VCS. Det finns ett antal sådana adaptrar, kallade “bryggor”. Här går vi igenom de du troligast stöter på i praktiken.

Git och Subversion

En stor del av utvecklingsprojekt med öppen källkod och många projekt i organisationer använder Subversion för att hantera sin källkod. Det har funnits i mer än ett decennium och var under större delen av den tiden det de facto‑valet av versionshanteringssystem för projekt med öppen källkod. Det liknar också CVS på många sätt, som var den dominerande aktören i versionshanteringsvärlden innan dess.

En av Gits stora finesser är en tvåvägsbrygga till Subversion som kallas git svn. Detta verktyg låter dig använda Git som en fullvärdig klient mot en Subversion‑server, så att du kan använda alla lokala funktioner i Git och sedan skicka till en Subversion‑server som om du använde Subversion lokalt. Det betyder att du kan arbeta med lokala grenar och sammanslagningar, använda köytan, använda ombasering och handplockning och så vidare, medan dina medarbetare fortsätter att arbeta på sina mörka och uråldriga sätt. Det är ett bra sätt att försiktigt introducera Git i organisationsmiljön och hjälpa dina kollegor att bli mer effektiva samtidigt som du driver på för att infrastrukturen ska anpassas för fullt Git‑stöd. Subversion‑bryggan är inkörsporten till DVCS‑världen.

git svn

Grundkommandot i Git för alla Subversion‑bryggkommandon är git svn. Det har ganska många underkommandon, så vi visar de vanligaste medan vi går igenom några enkla arbetsflöden.

Det är viktigt att notera att när du använder git svn så interagerar du med Subversion, som fungerar på ett helt annat sätt än Git. Även om du kan göra lokala grenar och sammanslagningar är det generellt bäst att hålla historiken så linjär som möjligt genom att ombasera ditt arbete och undvika att samtidigt arbeta mot ett Git‑fjärrkodförråd.

Skriv inte om historiken och försök skicka på nytt, och skicka inte upp till ett parallellt Git‑kodförråd för att samarbeta med andra Git‑utvecklare samtidigt. Subversion kan bara ha en enda linjär historik, och det är mycket lätt att förvirra den. Om du arbetar i ett team och några använder SVN medan andra använder Git, se till att alla använder SVN‑servern för samarbete – då blir livet enklare.

Sätta upp

För att demonstrera den här funktionaliteten behöver du ett typiskt SVN‑kodförråd som du har skrivåtkomst till. Om du vill följa exemplen måste du skapa en skrivbar kopia av ett SVN‑testkodförråd. För att göra det enkelt kan du använda ett verktyg som heter svnsync som följer med Subversion.

För att kunna följa med behöver du först skapa ett nytt lokalt Subversion‑kodförråd:

$ mkdir /tmp/test-svn
$ svnadmin create /tmp/test-svn

Sedan behöver du tillåta att alla användare kan ändra revprops – det enkla sättet är att lägga till ett pre-revprop-change‑skript som alltid avslutas med 0:

$ cat /tmp/test-svn/hooks/pre-revprop-change
#!/bin/sh
exit 0;
$ chmod +x /tmp/test-svn/hooks/pre-revprop-change

Nu kan du synka projektet till din lokala maskin genom att anropa svnsync init med käll- och mål‑kodförråd.

$ svnsync init file:///tmp/test-svn \
  http://your-svn-server.example.org/svn/

Detta sätter upp egenskaperna för att köra synken. Därefter kan du klona koden genom att köra:

$ svnsync sync file:///tmp/test-svn
Committed revision 1.
Copied properties for revision 1.
Transmitting file data .............................[...]
Committed revision 2.
Copied properties for revision 2.
[…]

Även om den här operationen ofta bara tar några minuter tar processen nästan en timme om du försöker kopiera det ursprungliga kodförrådet till ett annat fjärrkodförråd i stället för ett lokalt, även om det finns färre än 100 incheckningar. Subversion måste klona en revision i taget och sedan skicka tillbaka den till ett annat kodförråd – det är extremt ineffektivt, men det är det enda enkla sättet att göra detta.

Kom igång

Nu när du har ett Subversion‑kodförråd som du har skrivåtkomst till kan du gå igenom ett typiskt arbetsflöde. Du börjar med kommandot git svn clone, som importerar ett helt Subversion‑kodförråd till ett lokalt Git‑kodförråd. Kom ihåg att om du importerar från ett riktigt driftsatt Subversion‑kodförråd ska du ersätta file\:///tmp/test-svn här med webbadressen till ditt Subversion‑kodförråd:

$ git svn clone file:///tmp/test-svn -T trunk -b branches -t tags
Initialized empty Git repository in /private/tmp/progit/test-svn/.git/
r1 = dcbfb5891860124cc2e8cc616cded42624897125 (refs/remotes/origin/trunk)
    A	m4/acx_pthread.m4
    A	m4/stl_hash.m4
    A	java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java
    A	java/src/test/java/com/google/protobuf/WireFormatTest.java
…
r75 = 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae (refs/remotes/origin/trunk)
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/my-calc-branch, 75
Found branch parent: (refs/remotes/origin/my-calc-branch) 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae
Following parent with do_switch
Successfully followed parent
r76 = 0fb585761df569eaecd8146c71e58d70147460a2 (refs/remotes/origin/my-calc-branch)
Checked out HEAD:
  file:///tmp/test-svn/trunk r75

Det här motsvarar att först köra git svn init och sedan git svn fetch på den angivna webbadressen. Det kan ta en stund. Om till exempel testprojektet bara har runt 75 incheckningar och kodbasen inte är så stor måste Git ändå ta ut varje version, en i taget, och checka in den individuellt. För ett projekt med hundratals eller tusentals incheckningar kan detta bokstavligen ta timmar eller till och med dagar att bli klart.

Delen -T trunk -b branches -t tags talar om för Git att detta Subversion‑kodförråd följer de grundläggande konventionerna för grenar och taggar. Om du namnger din trunk, dina grenar eller taggar annorlunda kan du ändra dessa alternativ. Eftersom detta är så vanligt kan du ersätta hela delen med -s, som betyder standardlayout och innebär alla dessa alternativ. Följande kommando är likvärdigt:

$ git svn clone file:///tmp/test-svn -s

Nu bör du ha ett giltigt Git‑kodförråd som har importerat dina grenar och taggar:

$ git branch -a
* master
  remotes/origin/my-calc-branch
  remotes/origin/tags/2.0.2
  remotes/origin/tags/release-2.0.1
  remotes/origin/tags/release-2.0.2
  remotes/origin/tags/release-2.0.2rc1
  remotes/origin/trunk

Notera hur det här verktyget hanterar Subversion‑taggar som fjärrreferenser. Låt oss titta närmare med Git‑lågnivåkommandot show-ref:

$ git show-ref
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/heads/master
0fb585761df569eaecd8146c71e58d70147460a2 refs/remotes/origin/my-calc-branch
bfd2d79303166789fc73af4046651a4b35c12f0b refs/remotes/origin/tags/2.0.2
285c2b2e36e467dd4d91c8e3c0c0e1750b3fe8ca refs/remotes/origin/tags/release-2.0.1
cbda99cb45d9abcb9793db1d4f70ae562a969f1e refs/remotes/origin/tags/release-2.0.2
a9f074aa89e826d6f9d30808ce5ae3ffe711feda refs/remotes/origin/tags/release-2.0.2rc1
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/remotes/origin/trunk

Git gör inte detta när det klonar från en Git‑server; så här ser ett kodförråd med taggar ut efter en ny klon:

$ git show-ref
c3dcbe8488c6240392e8a5d7553bbffcb0f94ef0 refs/remotes/origin/master
32ef1d1c7cc8c603ab78416262cc421b80a8c2df refs/remotes/origin/branch-1
75f703a3580a9b81ead89fe1138e6da858c5ba18 refs/remotes/origin/branch-2
23f8588dde934e8f33c263c6d8359b2ae095f863 refs/tags/v0.1.0
7064938bd5e7ef47bfd79a685a62c1e2649e2ce7 refs/tags/v0.2.0
6dcb09b5b57875f334f61aebed695e2e4193db5e refs/tags/v1.0.0

Git uppdaterar taggarna direkt till refs/tags i stället för att behandla dem som fjärrgrenar.

Checka in tillbaka till Subversion

Nu när du har en arbetskatalog kan du göra lite arbete i projektet och skicka tillbaka dina incheckningar uppströms, där Git i praktiken fungerar som SVN‑klient. Om du redigerar en av filerna och checkar in den har du en incheckning som finns lokalt i Git men inte på Subversion‑servern:

$ git commit -am 'Adding git-svn instructions to the README'
[master 4af61fd] Adding git-svn instructions to the README
 1 file changed, 5 insertions(+)

Därefter behöver du skicka ändringen. Lägg märke till hur detta förändrar sättet du arbetar med Subversion – du kan göra flera incheckningar utan uppkoppling och sedan skicka dem allihop till Subversion‑servern på en gång. För att skicka till en Subversion‑server kör du kommandot git svn dcommit:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r77
    M	README.txt
r77 = 95e0222ba6399739834380eb10afcd73e0670bc5 (refs/remotes/origin/trunk)
No changes between 4af61fd05045e07598c553167e0f31c84fd6ffe1 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Det här tar alla dina lokala incheckningar ovanpå Subversion-koden, gör en Subversion-incheckning för var och en och skriver sedan om de lokala Git-incheckningarna med en unik identifierare. Det är viktigt, eftersom alla SHA-1-kontrollsummor för dina incheckningar ändras. Delvis av den anledningen är det ingen bra idé att arbeta samtidigt med Git‑baserade fjärrversioner av dina projekt parallellt med en Subversion‑server. Om du tittar på den senaste incheckningen kan du se den nya git-svn-id som lagts till:

$ git log -1
commit 95e0222ba6399739834380eb10afcd73e0670bc5
Author: ben <ben@0b684db3-b064-4277-89d1-21af03df0a68>
Date:   Thu Jul 24 03:08:36 2014 +0000

    Adding git-svn instructions to the README

    git-svn-id: file:///tmp/test-svn/trunk@77 0b684db3-b064-4277-89d1-21af03df0a68

Notera att SHA‑1‑kontrollsumman som ursprungligen började med 4af61fd när du checkade in nu börjar med 95e0222. Om du vill skicka till både en Git‑server och en Subversion‑server måste du skicka (dcommit) till Subversion‑servern först, eftersom den åtgärden ändrar din incheckningsdata.

Hämta in nya ändringar

Om du arbetar med andra utvecklare kommer någon av er förr eller senare att skicka, och sedan kommer den andre att försöka skicka en ändring som står i konflikt. Den ändringen blir nekad tills du sammanfogar deras arbete. I git svn ser det ut så här:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: d5837c4b461b7c0e018b49d12398769d2bfc240a and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 f414c433af0fd6734428cf9d2a9fd8ba00ada145 c80b6127dd04f5fcda218730ddf3a2da4eb39138 M	README.txt
Current branch master is up to date.
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

För att lösa situationen kan du köra git svn rebase, som uppdaterar ner alla ändringar på servern som du ännu inte har och ombaserar ditt arbete ovanpå det som ligger på servern:

$ git svn rebase
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: eaa029d99f87c5c822c5c29039d19111ff32ef46 and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 65536c6e30d263495c17d781962cfff12422693a b34372b25ccf4945fe5658fa381b075045e7702a M	README.txt
First, rewinding head to replay your work on top of it...
Applying: update foo
Using index info to reconstruct a base tree...
M	README.txt
Falling back to patching base and 3-way merge...
Auto-merging README.txt
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

Nu ligger allt ditt arbete ovanpå det som finns på Subversion‑servern, så du kan dcommit utan problem:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r85
    M	README.txt
r85 = 9c29704cc0bbbed7bd58160cfb66cb9191835cd8 (refs/remotes/origin/trunk)
No changes between 5762f56732a958d6cfda681b661d2a239cc53ef5 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Observera att till skillnad från Git, som kräver att du sammanfogar uppströmsarbete du ännu inte har lokalt innan du kan skicka, tvingar git svn dig att göra det bara om ändringarna står i konflikt (ungefär som Subversion fungerar). Om någon annan skickar upp en ändring i en fil och du sedan skickar upp en ändring i en annan fil fungerar din dcommit fint:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	configure.ac
Committed r87
    M	autogen.sh
r86 = d8450bab8a77228a644b7dc0e95977ffc61adff7 (refs/remotes/origin/trunk)
    M	configure.ac
r87 = f3653ea40cb4e26b6281cec102e35dcba1fe17c4 (refs/remotes/origin/trunk)
W: a0253d06732169107aa020390d9fefd2b1d92806 and refs/remotes/origin/trunk differ, using rebase:
:100755 100755 efa5a59965fbbb5b2b0a12890f1b351bb5493c18 e757b59a9439312d80d5d43bb65d4a7d0389ed6d M	autogen.sh
First, rewinding head to replay your work on top of it...

Det är viktigt att komma ihåg, eftersom resultatet kan bli ett projekttillstånd som inte fanns på någon av era datorer vid uppskickning. Om ändringarna är inkompatibla men inte i konflikt kan du få problem som är svåra att felsöka. Till skillnad från en Git-server kan du i Git testa hela tillståndet lokalt innan publicering, medan du i SVN inte kan vara säker på att tillstånden före och efter incheckning är identiska.

Du bör också köra detta kommando för att uppdatera ändringar från Subversion‑servern, även om du inte är redo att checka in själv. Du kan köra git svn fetch för att uppdatera den nya datan, men git svn rebase gör uppdateringen och uppdaterar sedan dina lokala incheckningar.

$ git svn rebase
    M	autogen.sh
r88 = c9c5f83c64bd755368784b444bc7a0216cc1e17b (refs/remotes/origin/trunk)
First, rewinding head to replay your work on top of it...
Fast-forwarded master to refs/remotes/origin/trunk.

Att köra git svn rebase då och då ser till att din kod alltid är uppdaterad. Du måste dock se till att din arbetskatalog är ren när du kör det. Om du har lokala ändringar måste du antingen lägga undan arbetet med git stash eller tillfälligt checka in det innan du kör git svn rebase – annars stoppar kommandot om det ser att ombaseringen leder till en sammanslagningskonflikt.

Problem med Git‑grenar

När du har blivit bekväm med ett Git‑arbetsflöde skapar du sannolikt ämnesgrenar, gör arbete på dem och sammanfogar dem sedan. Om du skickar upp till en Subversion‑server via git svn kan du vilja ombasera ditt arbete till en enda gren varje gång i stället för att sammanfoga grenar. Anledningen till att föredra ombasering är att Subversion har en linjär historik och inte hanterar sammanslagningar som Git gör, så git svn följer bara den första föräldern när den konverterar ögonblicksbilderna till Subversion‑incheckningar.

Anta att din historik ser ut så här: du skapade en gren experiment, gjorde två incheckningar och sammanfogade dem sedan tillbaka till master. När du kör dcommit ser du utdata som detta:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	CHANGES.txt
Committed r89
    M	CHANGES.txt
r89 = 89d492c884ea7c834353563d5d913c6adf933981 (refs/remotes/origin/trunk)
    M	COPYING.txt
    M	INSTALL.txt
Committed r90
    M	INSTALL.txt
    M	COPYING.txt
r90 = cb522197870e61467473391799148f6721bcf9a0 (refs/remotes/origin/trunk)
No changes between 71af502c214ba13123992338569f4669877f55fd and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Att köra dcommit på en gren med sammanfogad historik fungerar, förutom att när du tittar på historiken i ditt Git‑projekt har den inte skrivit om någon av incheckningarna du gjorde på grenen experiment – i stället dyker alla dessa ändringar upp i SVN‑versionen av den enda sammanslagningsincheckningen.

När någon annan klonar det arbetet ser de bara sammanslagningsincheckningen med allt arbete ihoptryckt i den, som om du körde git merge --squash; de ser inte incheckningsdata om var det kom ifrån eller när det incheckades.

Grenar i Subversion

Grenar i Subversion är inte samma sak som grenar i Git; om du kan undvika att använda det så mycket är det sannolikt bäst. Du kan dock skapa och checka in på grenar i Subversion med git svn.

Skapa en ny SVN-gren

För att skapa en ny gren i Subversion kör du git svn branch [new-branch]:

$ git svn branch opera
Copying file:///tmp/test-svn/trunk at r90 to file:///tmp/test-svn/branches/opera...
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/opera, 90
Found branch parent: (refs/remotes/origin/opera) cb522197870e61467473391799148f6721bcf9a0
Following parent with do_switch
Successfully followed parent
r91 = f1b64a3855d3c8dd84ee0ef10fa89d27f1584302 (refs/remotes/origin/opera)

Det här motsvarar Subversion‑kommandot svn copy trunk branches/opera och arbetar mot Subversion‑servern. Det är viktigt att notera att det inte växlar till den grenen; om du checkar in nu kommer incheckningen att hamna i trunk på servern, inte opera.

Växla aktiva grenar

Git räknar ut vilken gren dina dcommit går till genom att leta efter toppen på någon av dina Subversion‑grenar i historiken – du bör bara ha en, och det ska vara den senaste med ett git-svn-id i din nuvarande grenhistorik.

Om du vill arbeta på mer än en gren samtidigt kan du sätta upp lokala grenar för att dcommit till specifika Subversion‑grenar genom att starta dem på den importerade Subversion‑incheckningen för den grenen. Om du vill ha en gren opera som du kan arbeta på separat kan du köra:

$ git branch opera remotes/origin/opera

Om du nu vill sammanfoga grenen opera till trunk (din master‑gren) kan du göra det med en vanlig git merge. Men du behöver ange ett beskrivande incheckningsmeddelande (via -m), annars kommer sammanslagningen att säga “Merge branch opera” i stället för något användbart.

Kom ihåg att även om du använder git merge för den här operationen, och sammanslagningen sannolikt blir mycket enklare än den skulle vara i Subversion (eftersom Git automatiskt hittar rätt sammanslagningsbas åt dig), så är detta inte en normal Git‑sammanslagning. Du måste skicka datan till en Subversion‑server som inte kan hantera en incheckning som spårar mer än en förälder; så efter att du har skickat upp den ser den ut som en enda incheckning som tryckt ihop allt arbete från en annan gren i en enda incheckning. Efter att du har sammanfogat en gren till en annan kan du inte enkelt gå tillbaka och fortsätta arbeta på den grenen, som du normalt kan i Git. Kommandot dcommit som du kör raderar all information som säger vilken gren som sammanfogades in, så efterföljande beräkningar av sammanslagningsbaser blir fel – dcommit får resultatet av din git merge att se ut som om du körde git merge --squash. Tyvärr finns det inget bra sätt att undvika denna situation – Subversion kan inte lagra denna information, så du kommer alltid att vara begränsad av dess begränsningar medan du använder den som server. För att undvika problem bör du ta bort den lokala grenen (i detta fall opera) efter att du har sammanfogat den till trunk.

Subversion-kommandon

Verktygsuppsättningen git svn ger ett antal kommandon som underlättar övergången till Git genom att erbjuda funktionalitet som liknar det du hade i Subversion. Här är några kommandon som ger dig det Subversion brukade ge.

SVN-stil på historiken

Om du är van vid Subversion och vill se historiken i SVN‑utdataformat kan du köra git svn log för att visa din incheckningshistorik i SVN‑format:

$ git svn log
------------------------------------------------------------------------
r87 | schacon | 2014-05-02 16:07:37 -0700 (Sat, 02 May 2014) | 2 lines

autogen change

------------------------------------------------------------------------
r86 | schacon | 2014-05-02 16:00:21 -0700 (Sat, 02 May 2014) | 2 lines

Merge branch 'experiment'

------------------------------------------------------------------------
r85 | schacon | 2014-05-02 16:00:09 -0700 (Sat, 02 May 2014) | 2 lines

updated the changelog

Du bör känna till två viktiga saker om git svn log. För det första fungerar det frånkopplat, till skillnad från det riktiga svn log‑kommandot som frågar Subversion‑servern efter data. För det andra visar det bara incheckningar som har checkats in till Subversion‑servern. Lokala Git‑incheckningar som du inte har dcommitat syns inte; inte heller incheckningar som andra har gjort till Subversion‑servern under tiden. Det är mer som det senast kända läget för incheckningarna på Subversion‑servern.

SVN-annotering

Precis som git svn log‑kommandot simulerar svn log lokalt kan du få motsvarigheten till svn annotate genom att köra git svn blame [FILE]. Utdata ser ut så här:

$ git svn blame README.txt
 2   temporal Protocol Buffers - Google's data interchange format
 2   temporal Copyright 2008 Google Inc.
 2   temporal http://code.google.com/apis/protocolbuffers/
 2   temporal
22   temporal C++ Installation - Unix
22   temporal =======================
 2   temporal
79    schacon Committing in git-svn.
78    schacon
 2   temporal To build and install the C++ Protocol Buffer runtime and the Protocol
 2   temporal Buffer compiler (protoc) execute the following:
 2   temporal

Återigen visar det inte incheckningar som du har gjort lokalt i Git eller som har skickats upp till Subversion under tiden.

SVN-serverinformation

Du kan också få samma typ av information som svn info ger dig genom att köra git svn info:

$ git svn info
Path: .
URL: https://schacon-test.googlecode.com/svn/trunk
Repository Root: https://schacon-test.googlecode.com/svn
Repository UUID: 4c93b258-373f-11de-be05-5f7a86268029
Revision: 87
Node Kind: directory
Schedule: normal
Last Changed Author: schacon
Last Changed Rev: 87
Last Changed Date: 2009-05-02 16:07:37 -0700 (Sat, 02 May 2009)

Precis som blame och log körs detta lokalt och är bara uppdaterat till senaste kontakten med Subversion-servern.

Ignorera det Subversion ignorerar

Om du klonar ett Subversion‑kodförråd som har svn:ignore‑egenskaper satta någonstans vill du sannolikt skapa motsvarande .gitignore‑filer så att du inte råkar checka in filer du inte borde. git svn har två kommandon som hjälper till med detta. Det första är git svn create-ignore, som automatiskt skapar motsvarande .gitignore‑filer åt dig så att din nästa incheckning kan inkludera dem.

Det andra kommandot är git svn show-ignore, som skriver raderna du behöver lägga i en .gitignore‑fil till stdout så att du kan omdirigera utdata till projektets exkluderingsfil:

$ git svn show-ignore > .git/info/exclude

På så sätt skräpar du inte ner projektet med .gitignore‑filer. Det här är ett bra alternativ om du är den enda Git‑användaren i ett Subversion‑team och dina lagkamrater inte vill ha .gitignore‑filer i projektet.

Sammanfattning för git-svn

Verktygen i git svn är användbara om du sitter fast med en Subversion‑server eller på annat sätt befinner dig i en utvecklingsmiljö som kräver en Subversion‑server. Du bör dock se det som en stympad Git, annars stöter du på översättningsproblem som kan förvirra dig och dina medarbetare. För att undvika problem, försök att följa dessa riktlinjer:

  • Håll en linjär Git‑historik som inte innehåller sammanslagningsincheckningar gjorda med git merge. Ombasera allt arbete du gör utanför din huvudgren tillbaka på den; sammanfoga det inte.

  • Sätt inte upp och samarbeta på en separat Git‑server. Ha möjligen en för att snabba upp kloner för nya utvecklare, men skicka inte upp något till den som saknar en git-svn-id‑post. Du kan till och med vilja lägga till en pre-receive‑krok som kontrollerar varje incheckningsmeddelande efter ett git-svn-id och avvisar uppskickningar som innehåller incheckningar utan det.

Om du följer dessa riktlinjer kan arbete mot en Subversion‑server bli mer uthärdligt. Men om det är möjligt att flytta till en riktig Git‑server ger det teamet mycket mer.

Git och Mercurial

DVCS‑världen är större än bara Git. Faktum är att det finns många andra system i det här området, vart och ett med sin egen syn på hur distribuerad versionshantering ska fungera. Förutom Git är Mercurial det mest populära, och de två är mycket lika på många sätt.

Den goda nyheten, om du föredrar Gits klientbeteende men arbetar med ett projekt vars källkod hanteras med Mercurial, är att det finns ett sätt att använda Git som klient mot ett Mercurial‑värdat kodförråd. Eftersom Git kommunicerar med serverkodförråd via fjärrkodförråd är det ingen överraskning att den här bryggan är implementerad som en fjärrhjälpare. Projektet heter git-remote-hg och finns på https://github.com/felipec/git-remote-hg.

git-remote-hg

Först behöver du installera git-remote-hg. Det innebär i praktiken att lägga filen någonstans i din sökväg, till exempel så här:

$ curl -o ~/bin/git-remote-hg \
  https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg

…förutsatt att ~/bin ligger i din $PATH. Git‑fjärrkodförråd-hg har också ett beroende: mercurial‑biblioteket för Python. Om du har Python installerat räcker det med:

$ pip install mercurial

Om du inte har Python installerat, besök https://www.python.org/ och skaffa det först.

Det sista du behöver är Mercurial‑klienten. Gå till https://www.mercurial-scm.org/ och installera den om du inte redan har gjort det.

Nu är du redo. Allt du behöver är ett Mercurial‑kodförråd som du kan skicka till. Lyckligtvis kan varje Mercurial‑kodförråd fungera så, så vi använder bara "hello world"‑kodförrådet som alla använder för att lära sig Mercurial:

$ hg clone http://selenic.com/repo/hello /tmp/hello

Kom igång

Nu när vi har ett lämpligt “serversidan”‑kodförråd kan vi gå igenom ett typiskt arbetsflöde. Som du kommer att se är de här två systemen tillräckligt lika för att det inte ska bli särskilt mycket friktion.

Som alltid med Git börjar vi med att klona:

$ git clone hg::/tmp/hello /tmp/hello-git
$ cd /tmp/hello-git
$ git log --oneline --graph --decorate
* ac7955c (HEAD, origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master, master) Create a makefile
* 65bb417 Create a standard 'hello, world' program

Du kommer att märka att arbete med ett Mercurial‑kodförråd använder det vanliga kommandot git clone. Det beror på att git-remote-hg arbetar på ganska låg nivå och använder en mekanism som liknar hur Gits HTTP/S‑protokoll är implementerat (fjärrhjälpare). Eftersom Git och Mercurial båda är utformade för att varje klient ska ha en fullständig kopia av kodförrådshistoriken gör kommandot en full klon, inklusive hela projektets historik, och gör det ganska snabbt.

Kommandot log visar två incheckningar, där den senaste pekas ut av en hel svärm av referenser. Det visar sig att några av dem inte finns där. Låt oss titta på vad som verkligen ligger i .git‑katalogen:

$ tree .git/refs
.git/refs
├── heads
│   └── master
├── hg
│   └── origin
│       ├── bookmarks
│       │   └── master
│       └── branches
│           └── default
├── notes
│   └── hg
├── remotes
│   └── origin
│       └── HEAD
└── tags

9 directories, 5 files

Git‑fjärrkodförråd-hg försöker göra saker mer idiomatiska för Git, men under huven hanterar det den konceptuella mappningen mellan två något olika system. Katalogen refs/hg är där de faktiska fjärrreferenserna lagras. Till exempel är refs/hg/origin/branches/default en Git‑reffil som innehåller SHA‑1:an som börjar med “ac7955c”, vilket är incheckningen som master pekar på. Så katalogen refs/hg är lite som ett låtsas‑refs/remotes/origin, men med den extra distinktionen mellan bokmärken och grenar.

Filen notes/hg är startpunkten för hur git-remote-hg mappar Git‑incheckningshashar till Mercurial‑ändringsuppsättnings‑ID:n. Låt oss utforska lite:

$ cat notes/hg
d4c10386...

$ git cat-file -p d4c10386...
tree 1781c96...
author remote-hg <> 1408066400 -0800
committer remote-hg <> 1408066400 -0800

Notes for master

$ git ls-tree 1781c96...
100644 blob ac9117f...	65bb417...
100644 blob 485e178...	ac7955c...

$ git cat-file -p ac9117f
0a04b987be5ae354b710cefeba0e2d9de7ad41a9

refs/notes/hg pekar på ett träd, som i Gits objektdatabas är en lista över andra objekt med namn. git ls-tree skriver ut läge, typ, objekthash och filnamn för objekt i ett träd. När vi gräver oss ner till ett av trädobjekten hittar vi inuti en blob som heter “ac9117f” (SHA‑1‑hashen för incheckningen som master pekar på), med innehållet “0a04b98” (vilket är ID:t för Mercurial‑ändringsuppsättningen längst fram på grenen default).

Den goda nyheten är att vi för det mesta inte behöver bekymra oss om allt detta. Det typiska arbetsflödet skiljer sig inte särskilt mycket från att arbeta med ett Git‑fjärrkodförråd.

Det finns en sak till vi bör ta hand om innan vi fortsätter: ignoreringsregler. Mercurial och Git använder en mycket liknande mekanism för detta, men du vill sannolikt inte checka in en .gitignore‑fil i ett Mercurial‑kodförråd. Lyckligtvis har Git ett sätt att ignorera filer lokalt i ett kodförråd på disk, och Mercurial‑formatet är kompatibelt med Git, så du behöver bara kopiera det:

$ cp .hgignore .git/info/exclude

Filen .git/info/exclude fungerar precis som en .gitignore, men ingår inte i incheckningar.

Arbetsflöde

Låt oss anta att vi har gjort lite arbete och gjort några incheckningar på grenen master, och att du är redo att skicka det till fjärrkodförrådet. Så här ser vårt kodförråd ut just nu:

$ git log --oneline --graph --decorate
* ba04a2a (HEAD, master) Update makefile
* d25d16f Goodbye
* ac7955c (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Create a makefile
* 65bb417 Create a standard 'hello, world' program

Vår gren master ligger två incheckningar före origin/master, men de två incheckningarna finns bara på vår lokala maskin. Låt oss se om någon annan har gjort viktigt arbete samtidigt:

$ git fetch
From hg::/tmp/hello
   ac7955c..df85e87  master     -> origin/master
   ac7955c..df85e87  branches/default -> origin/branches/default
$ git log --oneline --graph --decorate --all
* 7b07969 (refs/notes/hg) Notes for default
* d4c1038 Notes for master
* df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
| * ba04a2a (HEAD, master) Update makefile
| * d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard 'hello, world' program

Eftersom vi använde flaggan --all ser vi “notes”‑referenserna som används internt av git-remote-hg, men vi kan ignorera dem. Resten är vad vi förväntade oss; origin/master har gått framåt med en incheckning, och vår historik har nu divergerat. Till skillnad från de andra systemen vi arbetar med i det här kapitlet klarar Mercurial av sammanslagningar, så vi behöver inte göra något avancerat.

$ git merge origin/master
Auto-merging hello.c
Merge made by the 'recursive' strategy.
 hello.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --decorate
*   0c64627 (HEAD, master) Merge remote-tracking branch 'origin/master'
|\
| * df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
* | ba04a2a Update makefile
* | d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard 'hello, world' program

Perfekt. Vi kör testerna och allt går igenom, så vi är redo att dela vårt arbete med resten av teamet:

$ git push
To hg::/tmp/hello
   df85e87..0c64627  master -> master

Det var allt! Om du tittar i Mercurial‑kodförrådet ser du att detta gjorde det vi förväntade oss:

$ hg log -G --style compact
o    5[tip]:4,2   dc8fa4f932b8   2014-08-14 19:33 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   64f27bcefc35   2014-08-14 19:27 -0700   ben
| |    Update makefile
| |
| o  3:1   4256fc29598f   2014-08-14 19:27 -0700   ben
| |    Goodbye
| |
@ |  2   7db0b4848b3c   2014-08-14 19:30 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard 'hello, world' program

Ändringsuppsättningen med nummer 2 skapades av Mercurial, och ändringsuppsättningarna 3 och 4 skapades av git-remote-hg genom att skicka incheckningar som gjorts i Git.

Grenar och bokmärken

Git har bara en sorts gren: en referens som flyttar sig när incheckningar görs. I Mercurial kallas den här typen av referens för ett “bokmärke” och det fungerar på ungefär samma sätt som en Git‑gren.

Mercurials begrepp “gren” är mer tungviktigt. Grenen som en ändringsuppsättning görs på registreras tillsammans med ändringsuppsättningen, vilket betyder att den alltid kommer att finnas i kodförrådshistoriken. Här är ett exempel på en incheckning som gjordes på grenen develop:

$ hg log -l 1
changeset:   6:8f65e5e02793
branch:      develop
tag:         tip
user:        Ben Straub <ben@straub.cc>
date:        Thu Aug 14 20:06:38 2014 -0700
summary:     More documentation

Notera raden som börjar med “branch”. Git kan egentligen inte replikera detta (och behöver inte heller; båda typerna av grenar kan representeras som en Git‑referens), men git-remote-hg behöver förstå skillnaden eftersom Mercurial gör det.

Att skapa Mercurial‑bokmärken är lika enkelt som att skapa Git‑grenar. På Git‑sidan:

$ git checkout -b featureA
Switched to a new branch 'featureA'
$ git push origin featureA
To hg::/tmp/hello
 * [new branch]      featureA -> featureA

Det är allt som behövs. På Mercurial‑sidan ser det ut så här:

$ hg bookmarks
   featureA                  5:bd5ac26f11f9
$ hg log --style compact -G
@  6[tip]   8f65e5e02793   2014-08-14 20:06 -0700   ben
|    More documentation
|
o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| |    update makefile
| |
| o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |    goodbye
| |
o |  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard 'hello, world' program

Notera den nya taggen [featureA] på revision 5. De fungerar exakt som Git‑grenar på Git‑sidan, med ett undantag: du kan inte ta bort ett bokmärke från Git‑sidan (detta är en begränsning i fjärrhjälparen).

Du kan också arbeta på en “tungviktig” Mercurial‑gren: lägg bara en gren i namnrymden branches:

$ git checkout -b branches/permanent
Switched to a new branch 'branches/permanent'
$ vi Makefile
$ git commit -am 'A permanent change'
$ git push origin branches/permanent
To hg::/tmp/hello
 * [new branch]      branches/permanent -> branches/permanent

Så här ser det ut på Mercurial‑sidan:

$ hg branches
permanent                      7:a4529d07aad4
develop                        6:8f65e5e02793
default                        5:bd5ac26f11f9 (inactive)
$ hg log -G
o  changeset:   7:a4529d07aad4
|  branch:      permanent
|  tag:         tip
|  parent:      5:bd5ac26f11f9
|  user:        Ben Straub <ben@straub.cc>
|  date:        Thu Aug 14 20:21:09 2014 -0700
|  summary:     A permanent change
|
| @  changeset:   6:8f65e5e02793
|/   branch:      develop
|    user:        Ben Straub <ben@straub.cc>
|    date:        Thu Aug 14 20:06:38 2014 -0700
|    summary:     More documentation
|
o    changeset:   5:bd5ac26f11f9
|\   bookmark:    featureA
| |  parent:      4:0434aaa6b91f
| |  parent:      2:f098c7f45c4f
| |  user:        Ben Straub <ben@straub.cc>
| |  date:        Thu Aug 14 20:02:21 2014 -0700
| |  summary:     Merge remote-tracking branch 'origin/master'
[...]

Grennamnet “permanent” registrerades tillsammans med ändringsuppsättningen markerad 7.

Från Git‑sidan är arbetet med båda dessa grenstilar detsamma: växla bara till, checka in, uppdatera, sammanfoga, dra och skicka som du brukar. En sak du bör känna till är att Mercurial inte stödjer omskrivning av historik, bara tillägg. Så här ser vårt Mercurial‑kodförråd ut efter en interaktiv ombasering och en tvångsuppskickning:

$ hg log --style compact -G
o  10[tip]   99611176cbc9   2014-08-14 20:21 -0700   ben
|    A permanent change
|
o  9   f23e12f939c3   2014-08-14 20:01 -0700   ben
|    Add some documentation
|
o  8:1   c16971d33922   2014-08-14 20:00 -0700   ben
|    goodbye
|
| o  7:5   a4529d07aad4   2014-08-14 20:21 -0700   ben
| |    A permanent change
| |
| | @  6   8f65e5e02793   2014-08-14 20:06 -0700   ben
| |/     More documentation
| |
| o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
| |\     Merge remote-tracking branch 'origin/master'
| | |
| | o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| | |    update makefile
| | |
+---o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |      goodbye
| |
| o  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

Ändringsuppsättningarna 8, 9 och 10 har skapats och hör till grenen permanent, men de gamla ändringsuppsättningarna finns fortfarande kvar. Det kan vara mycket förvirrande för dina teamkamrater som använder Mercurial, så försök undvika det.

Sammanfattning för Mercurial

Git och Mercurial är tillräckligt lika för att arbete över gränsen ska vara ganska smärtfritt. Om du undviker att ändra historik som har lämnat din maskin (som generellt rekommenderas) kanske du inte ens märker att den andra änden är Mercurial.

Git och Perforce

Perforce är ett mycket populärt versionshanteringssystem i organisationsmiljöer. Det har funnits sedan 1995, vilket gör det till det äldsta systemet som behandlas i det här kapitlet. Därför är det utformat med sin tids begränsningar; det antar att du alltid är uppkopplad mot en enda central server och att bara en version hålls på den lokala disken. För tydlighetens skull är dess funktioner och begränsningar väl lämpade för flera specifika problem, men det finns många projekt som använder Perforce där Git faktiskt skulle fungera bättre.

Det finns två alternativ om du vill kombinera användningen av Perforce och Git. Det första vi går igenom är “Git Fusion”‑bryggan från Perforces skapare, som låter dig exponera underträd i din Perforce‑depå som läs‑ och skrivbara Git‑kodförråd. Det andra är git-p4, en brygga på klientsidan som låter dig använda Git som Perforce‑klient utan att kräva någon omkonfigurering av Perforce‑servern.

Git Fusion

Perforce tillhandahåller en produkt som heter Git Fusion (tillgänglig på https://web.archive.org/web/20180101000000/https://www.perforce.com/manuals/git-fusion/), som synkroniserar en Perforce‑server med Git‑kodförråd på serversidan.

Sätta upp

I våra exempel använder vi den enklaste installationsmetoden för Git Fusion, vilket är att ladda ner en virtuell maskin som kör Perforce‑demonen och Git Fusion. Du kan hämta den virtuella maskinavbildningen från https://www.perforce.com/downloads, och när nedladdningen är klar importerar du den i din favorit‑virtualiseringsmjukvara (vi använder VirtualBox).

När du startar maskinen första gången ber den dig att anpassa lösenordet för tre Linux‑användare (root, perforce och git) samt ange ett instansnamn som kan användas för att skilja installationen från andra på samma nätverk. När det är klart ser du detta:

Git Fusions uppstartsskärm för den virtuella maskinen
Figur 169. Git Fusions uppstartsskärm för den virtuella maskinen

Du bör notera IP‑adressen som visas här; vi använder den senare. Nästa steg är att skapa en Perforce‑användare. Välj alternativet “Login” längst ner och tryck enter (eller SSH:a till maskinen) och logga in som root. Använd sedan dessa kommandon för att skapa en användare:

$ p4 -p localhost:1666 -u super user -f john
$ p4 -p localhost:1666 -u john passwd
$ exit

Det första kommandot öppnar en VI‑redigerare för att anpassa användaren, men du kan acceptera standardvärdena genom att skriva :wq och trycka enter. Det andra ber dig att ange ett lösenord två gånger. Det var allt vi behövde göra i ett skal, så avsluta sessionen.

Nästa sak du behöver göra för att följa exemplet är att säga till Git att inte verifiera SSL‑certifikat. Git Fusion‑avbildningen kommer med ett certifikat, men det är för en domän som inte matchar IP‑adressen på din virtuella maskin, så Git kommer att neka HTTPS‑anslutningen. Om detta ska vara en permanent installation, titta i Perforce Git Fusion‑manualen för att installera ett annat certifikat; för vårt exempel räcker detta:

$ export GIT_SSL_NO_VERIFY=true

Nu kan vi testa att allt fungerar.

$ git clone https://10.0.1.254/Talkhouse
Cloning into 'Talkhouse'...
Username for 'https://10.0.1.254': john
Password for 'https://john@10.0.1.254':
remote: Counting objects: 630, done.
remote: Compressing objects: 100% (581/581), done.
remote: Total 630 (delta 172), reused 0 (delta 0)
Receiving objects: 100% (630/630), 1.22 MiB | 0 bytes/s, done.
Resolving deltas: 100% (172/172), done.
Checking connectivity... done.

Avbildningen av den virtuella maskinen levereras med ett exempelprojekt som du kan klona. Här klonar vi över HTTPS, med användaren john som vi skapade ovan; Git ber om inloggningsuppgifter för anslutningen, men autentiseringsmellanlagringen gör att vi kan hoppa över steget vid framtida anrop.

Fusion-konfiguration

När du har installerat Git Fusion vill du justera konfigurationen. Det är ganska enkelt att göra med din favorit‑Perforce‑klient; mappa bara katalogen //.git-fusion på Perforce‑servern till din arbetskatalog. Filstrukturen ser ut så här:

$ tree
.
├── objects
│   ├── repos
│   │   └── [...]
│   └── trees
│       └── [...]
│
├── p4gf_config
├── repos
│   └── Talkhouse
│       └── p4gf_config
└── users
    └── p4gf_usermap

498 directories, 287 files

Katalogen objects används internt av Git Fusion för att mappa Perforce‑objekt till Git och vice versa; du behöver inte röra något där. Det finns en global p4gf_config‑fil i den här katalogen, samt en för varje kodförråd – det är konfigurationsfilerna som avgör hur Git Fusion beter sig. Låt oss titta på filen i roten:

[repo-creation]
charset = utf8

[git-to-perforce]
change-owner = author
enable-git-branch-creation = yes
enable-swarm-reviews = yes
enable-git-merge-commits = yes
enable-git-submodules = yes
preflight-commit = none
ignore-author-permissions = no
read-permission-check = none
git-merge-avoidance-after-change-num = 12107

[perforce-to-git]
http-url = none
ssh-url = none

[@features]
imports = False
chunked-push = False
matrix2 = False
parallel-push = False

[authentication]
email-case-sensitivity = no

Vi går inte in på betydelsen av dessa flaggor här, men notera att det bara är en INI‑formaterad textfil, ungefär som Git använder för konfiguration. Filen anger de globala alternativen, som sedan kan skrivas över av kodförrådsspecifika konfigurationsfiler, som repos/Talkhouse/p4gf_config. Om du öppnar denna fil ser du en [@repo]‑sektion med vissa inställningar som skiljer sig från de globala standarderna. Du ser också sektioner som ser ut så här:

[Talkhouse-master]
git-branch-name = master
view = //depot/Talkhouse/main-dev/... ...

Det här är en mappning mellan en Perforce‑gren och en Git‑gren. Sektionen kan heta vad du vill, så länge namnet är unikt. git-branch-name låter dig omvandla en depot‑sökväg som skulle vara besvärlig i Git till ett mer vänligt namn. Inställningen view styr hur Perforce‑filer mappas in i Git‑kodförrådet med hjälp av standard‑syntax för view‑mappning. Mer än en mappning kan anges, som i detta exempel:

[multi-project-mapping]
git-branch-name = master
view = //depot/project1/main/... project1/...
       //depot/project2/mainline/... project2/...

På så sätt kan du, om din normala arbetsytemappning inkluderar förändringar i katalogstrukturen, replikera det med ett Git‑kodförråd.

Den sista filen vi tar upp är users/p4gf_usermap, som mappar Perforce‑användare till Git‑användare, och som du kanske inte ens behöver. När man konverterar från en Perforce‑ändringsuppsättning till en Git‑incheckning är Git Fusions standardbeteende att slå upp Perforce‑användaren och använda e‑postadressen och fullständiga namnet som är lagrat där i Git‑fältet för författare/incheckare. När man konverterar åt andra hållet är standarden att slå upp Perforce‑användaren med e‑postadressen som finns i Git‑incheckningens author‑fält och skicka in ändringsuppsättningen som den användaren (med tillhörande behörigheter). I de flesta fall fungerar detta alldeles utmärkt, men titta på följande mappningsfil:

john john@example.com "John Doe"
john johnny@appleseed.net "John Doe"
bob employeeX@example.com "Anon X. Mouse"
joe employeeY@example.com "Anon Y. Mouse"

Varje rad har formatet <user> <email> "<full name>" och skapar en användarmappning. De två första raderna mappar två olika e‑postadresser till samma Perforce‑användarkonto. Det här är användbart om du har skapat Git‑incheckningar under flera olika e‑postadresser (eller byter e‑postadress) men vill att de ska mappas till samma Perforce‑användare. När en Git‑incheckning skapas från en Perforce‑ändringsuppsättning används den första raden som matchar Perforce‑användaren för Git‑författarinformationen.

De två sista raderna maskerar Bobs och Joes riktiga namn och e‑postadresser i Git‑incheckningarna som skapas. Det här är praktiskt om du vill öppna källkoden för ett internt projekt men inte vill publicera personalregistret för hela världen. Observera att e‑postadresserna och fullständiga namnen bör vara unika, om du inte vill att alla Git‑incheckningar ska tillskrivas en enda fiktiv författare.

Arbetsflöde

Perforce Git Fusion är en tvåvägsbrygga mellan Perforce och Git. Låt oss titta på hur det känns att arbeta från Git‑sidan. Vi antar att vi har mappat in projektet “Jam” med en konfigurationsfil som visas ovan, vilket vi kan klona så här:

$ git clone https://10.0.1.254/Jam
Cloning into 'Jam'...
Username for 'https://10.0.1.254': john
Password for 'https://john@10.0.1.254':
remote: Counting objects: 2070, done.
remote: Compressing objects: 100% (1704/1704), done.
Receiving objects: 100% (2070/2070), 1.21 MiB | 0 bytes/s, done.
remote: Total 2070 (delta 1242), reused 0 (delta 0)
Resolving deltas: 100% (1242/1242), done.
Checking connectivity... done.
$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/rel2.1
$ git log --oneline --decorate --graph --all
* 0a38c33 (origin/rel2.1) Create Jam 2.1 release branch.
| * d254865 (HEAD, origin/master, origin/HEAD, master) Upgrade to latest metrowerks on Beos -- the Intel one.
| * bd2f54a Put in fix for jam's NT handle leak.
| * c0f29e7 Fix URL in a jam doc
| * cc644ac Radstone's lynx port.
[...]

Första gången du gör detta kan det ta en stund. Det som händer är att Git Fusion konverterar alla relevanta ändringsuppsättningar i Perforce‑historiken till Git‑incheckningar. Det sker lokalt på servern, så det går relativt snabbt, men om du har mycket historik kan det ändå ta tid. Efterföljande uppdateringar gör inkrementell konvertering, så det känns mer som Gits egen hastighet.

Som du ser liknar vårt kodförråd vilket annat Git‑kodförråd som helst. Det finns tre grenar, och Git har hjälpsamt skapat en lokal master‑gren som spårar origin/master. Låt oss göra lite arbete och skapa ett par nya incheckningar:

# ...
$ git log --oneline --decorate --graph --all
* cfd46ab (HEAD, master) Add documentation for new feature
* a730d77 Whitespace
* d254865 (origin/master, origin/HEAD) Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

Vi har två nya incheckningar. Nu kontrollerar vi om någon annan har arbetat:

$ git fetch
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://10.0.1.254/Jam
   d254865..6afeb15  master     -> origin/master
$ git log --oneline --decorate --graph --all
* 6afeb15 (origin/master, origin/HEAD) Update copyright
| * cfd46ab (HEAD, master) Add documentation for new feature
| * a730d77 Whitespace
|/
* d254865 Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

Det ser ut som att någon gjorde det! Du skulle inte veta det från den här vyn, men incheckningen 6afeb15 skapades faktiskt med en Perforce‑klient. Den ser bara ut som en annan incheckning ur Gits perspektiv, vilket är precis poängen. Låt oss se hur Perforce‑servern hanterar en sammanslagningsincheckning:

$ git merge origin/master
Auto-merging README
Merge made by the 'recursive' strategy.
 README | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 6), reused 0 (delta 0)
remote: Perforce: 100% (3/3) Loading commit tree into memory...
remote: Perforce: 100% (5/5) Finding child commits...
remote: Perforce: Running git fast-export...
remote: Perforce: 100% (3/3) Checking commits...
remote: Processing will continue even if connection is closed.
remote: Perforce: 100% (3/3) Copying changelists...
remote: Perforce: Submitting new Git commit objects to Perforce: 4
To https://10.0.1.254/Jam
   6afeb15..89cba2b  master -> master

Git tycker att det fungerade. Låt oss titta på historiken för filen README ur Perforces perspektiv, med hjälp av revisionsgraf‑funktionen i p4v:

Perforce‑revisionsgraf som resultat av Git‑uppskickning
Figur 170. Perforce‑revisionsgraf som resultat av Git‑uppskickning

Om du aldrig har sett den här vyn tidigare kan den verka förvirrande, men den visar samma begrepp som en grafisk visare för Git‑historik. Vi tittar på historiken för filen README, så katalogträdet uppe till vänster visar bara den filen när den dyker upp i olika grenar. Uppe till höger har vi en visuell graf över hur olika revisioner av filen hänger ihop, och översiktsvyn av grafen finns nere till höger. Resten av vyn är detaljvyn för den valda revisionen (i det här fallet 2).

En sak att lägga märke till är att grafen ser exakt ut som den i Gits historik. Perforce hade ingen namngiven gren för att lagra incheckningarna 1 och 2, så den skapade en “anonym”‑gren i katalogen .git-fusion för att hålla dem. Detta händer även för namngivna Git‑grenar som inte motsvarar en namngiven Perforce‑gren (och du kan senare mappa dem till en Perforce‑gren med hjälp av konfigurationsfilen).

Det mesta av detta sker bakom kulisserna, men slutresultatet är att en person i ett team kan använda Git, en annan kan använda Perforce, och ingen av dem behöver veta om den andres val.

Sammanfattning för Git Fusion

Om du har (eller kan få) åtkomst till din Perforce‑server är Git Fusion ett utmärkt sätt att få Git och Perforce att prata med varandra. Det krävs en del konfiguration, men inlärningskurvan är inte särskilt brant. Det här är en av få sektioner i kapitlet där varningar om att använda Gits fulla kraft inte dyker upp. Det betyder inte att Perforce blir nöjd med allt du kastar på det – om du försöker skriva om historik som redan har skickats upp kommer Git Fusion att neka det – men Git Fusion försöker verkligen kännas inbyggt. Du kan till och med använda Git‑submoduler (även om de ser märkliga ut för Perforce‑användare) och sammanfoga grenar (detta registreras som en integration på Perforce‑sidan).

Om du inte kan övertyga administratören för din server att sätta upp Git Fusion finns det ändå ett sätt att använda dessa verktyg tillsammans.

Git-p4

Git-p4 är en tvåvägsbrygga mellan Git och Perforce. Den körs helt och hållet inne i ditt Git‑kodförråd, så du behöver ingen särskild åtkomst till Perforce‑servern (förutom användaruppgifter förstås). Git-p4 är inte lika flexibel eller komplett som Git Fusion, men den låter dig göra det mesta du vill utan att behöva ingripa i servermiljön.

Notera

Du behöver verktyget p4 någonstans i din PATH för att arbeta med git-p4. I skrivande stund är det gratis tillgängligt på https://www.perforce.com/downloads/helix-command-line-client-p4.

Sätta upp

För exemplens skull kör vi Perforce‑servern från Git Fusion‑OVA:n som visats ovan, men vi hoppar över Git Fusion‑servern och går direkt mot Perforce‑versionshanteringen.

För att använda kommandoradsklienten p4 (som git-p4 bygger på) behöver du sätta ett par miljövariabler:

$ export P4PORT=10.0.1.254:1666
$ export P4USER=john
Kom igång

Som med allt i Git är det första kommandot att klona:

$ git p4 clone //depot/www/live www-shallow
Importing from //depot/www/live into www-shallow
Initialized empty Git repository in /private/tmp/www-shallow/.git/
Doing initial import of //depot/www/live/ from revision #head into refs/remotes/p4/master

I Git-termer skapar detta en “ytlig” klon: bara den senaste Perforce-revisionen importeras, eftersom Perforce inte är byggt för att leverera varje revision till varje användare. Detta räcker för att använda Git som Perforce‑klient, men för andra ändamål räcker det inte.

När det är klart har vi ett fullt fungerande Git‑kodförråd:

$ cd myproject
$ git log --oneline --all --graph --decorate
* 70eaf78 (HEAD, p4/master, p4/HEAD, master) Initial import of //depot/www/live/ from the state at revision #head

Notera att det finns ett “p4”‑fjärrkodförråd för Perforce‑servern, men i övrigt ser allt ut som en standardklon. Det är dock lite missvisande; det finns egentligen inget fjärrkodförråd där.

$ git remote -v

Det finns inga fjärrkodförråd alls i det här kodförrådet. Git-p4 har skapat vissa referenser som representerar serverns tillstånd, och de ser ut som fjärrreferenser i git log, men de hanteras inte av Git själv och du kan inte skicka till dem.

Arbetsflöde

Okej, låt oss göra lite arbete. Anta att du har gjort en del framsteg på en mycket viktig funktion och är redo att visa den för resten av teamet.

$ git log --oneline --all --graph --decorate
* 018467c (HEAD, master) Change page title
* c0fb617 Update link
* 70eaf78 (p4/master, p4/HEAD) Initial import of //depot/www/live/ from the state at revision #head

Vi har gjort två nya incheckningar som vi är redo att skicka in till Perforce‑servern. Låt oss se om någon annan arbetade i dag:

$ git p4 sync
git p4 sync
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12142 (100%)
$ git log --oneline --all --graph --decorate
* 75cd059 (p4/master, p4/HEAD) Update copyright
| * 018467c (HEAD, master) Change page title
| * c0fb617 Update link
|/
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Det ser ut som att de gjorde det, och master och p4/master har divergerat. Perforces grensystem är inte alls som Gits, så det är inte rimligt att skicka in sammanslagningsincheckningar. Git-p4 rekommenderar att du ombaserar dina incheckningar och har till och med en genväg för att göra det:

$ git p4 rebase
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
No changes to import!
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
Applying: Update link
Applying: Change page title
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Du ser det kanske av utdata, men git p4 rebase är en genväg för git p4 sync följt av git rebase p4/master. Det är lite smartare än så, särskilt när man arbetar med flera grenar, men detta är en bra approximation.

Nu är vår historik linjär igen och vi är redo att bidra med våra ändringar tillbaka till Perforce. Kommandot git p4 submit försöker skapa en ny Perforce‑revision för varje Git‑incheckning mellan p4/master och master. När du kör det hamnar du i din favoritredigerare och filens innehåll ser ungefär ut så här:

# A Perforce Change Specification.
#
#  Change:      The change number. 'new' on a new changelist.
#  Date:        The date this specification was last modified.
#  Client:      The client on which the changelist was created.  Read-only.
#  User:        The user who created the changelist.
#  Status:      Either 'pending' or 'submitted'. Read-only.
#  Type:        Either 'public' or 'restricted'. Default is 'public'.
#  Description: Comments about the changelist.  Required.
#  Jobs:        What opened jobs are to be closed by this changelist.
#               You may delete jobs from this list.  (New changelists only.)
#  Files:       What opened files from the default changelist are to be added
#               to this changelist.  You may delete files from this list.
#               (New changelists only.)

Change:  new

Client:  john_bens-mbp_8487

User: john

Status:  new

Description:
   Update link

Files:
   //depot/www/live/index.html   # edit


######## git author ben@straub.cc does not match your p4 account.
######## Use option --preserve-user to modify authorship.
######## Variable git-p4.skipUserNameCheck hides this message.
######## everything below this line is just the diff #######
--- //depot/www/live/index.html  2014-08-31 18:26:05.000000000 0000
+++ /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/index.html   2014-08-31 18:26:05.000000000 0000
@@ -60,7 +60,7 @@
 </td>
 <td valign=top>
 Source and documentation for
-<a href="http://www.perforce.com/jam/jam.html">
+<a href="jam.html">
 Jam/MR</a>,
 a software build tool.
 </td>

Det här är i stort sett samma innehåll som du skulle se genom att köra p4 submit, förutom sakerna i slutet som git-p4 vänligt har lagt till. Git-p4 försöker respektera dina Git‑ och Perforce‑inställningar var för sig när det behöver ange ett namn för en incheckning eller ändringsuppsättning, men i vissa fall vill du skriva över det. Till exempel, om Git‑incheckningen du importerar skrevs av en bidragsgivare som inte har ett Perforce‑konto, vill du kanske fortfarande att den resulterande ändringsuppsättningen ska se ut som om de skrev den (och inte du).

Git-p4 har hjälpsamt importerat meddelandet från Git‑incheckningen som innehåll för denna Perforce‑ändringsuppsättning, så allt vi behöver göra är att spara och avsluta, två gånger (en gång per incheckning). Den resulterande skalsutmatningen ser ungefär ut så här:

$ git p4 submit
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Synchronizing p4 checkout...
... - file(s) up-to-date.
Applying dbac45b Update link
//depot/www/live/index.html#4 - opened for edit
Change 12143 created with 1 open file(s).
Submitting change 12143.
Locking 1 files ...
edit //depot/www/live/index.html#5
Change 12143 submitted.
Applying 905ec6a Change page title
//depot/www/live/index.html#5 - opened for edit
Change 12144 created with 1 open file(s).
Submitting change 12144.
Locking 1 files ...
edit //depot/www/live/index.html#6
Change 12144 submitted.
All commits applied!
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12144 (100%)
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
$ git log --oneline --all --graph --decorate
* 775a46f (HEAD, p4/master, p4/HEAD, master) Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Resultatet motsvarar i praktiken att vi just körde git push, vilket ligger nära det som faktiskt hände.

Notera att under denna process omvandlas varje Git‑incheckning till en Perforce‑ändringsuppsättning; om du vill sammanfoga dem till en enda ändringsuppsättning kan du göra det med en interaktiv ombasering innan du kör git p4 submit. Notera också att SHA‑1‑hasharna för alla incheckningar som skickades in som ändringsuppsättningar har förändrats; detta beror på att git-p4 lägger till en rad i slutet av varje incheckning som den konverterar:

$ git log -1
commit 775a46f630d8b46535fc9983cf3ebe6b9aa53145
Author: John Doe <john@example.com>
Date:   Sun Aug 31 10:31:44 2014 -0800

    Change page title

    [git-p4: depot-paths = "//depot/www/live/": change = 12144]

Vad händer om du försöker skicka in en sammanslagningsincheckning? Låt oss prova. Här är läget vi har försatt oss i:

$ git log --oneline --all --graph --decorate
* 3be6fd8 (HEAD, master) Correct email address
*   1dcbf21 Merge remote-tracking branch 'p4/master'
|\
| * c4689fc (p4/master, p4/HEAD) Grammar fix
* | cbacd0a Table borders: yes please
* | b4959b6 Trademark
|/
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Git‑ och Perforce‑historiken divergerar efter 775a46f. Git‑sidan har två incheckningar, sedan en sammanslagningsincheckning med Perforce‑toppen, och sedan en till incheckning. Vi ska försöka skicka in dessa ovanpå en enda ändringsuppsättning på Perforce‑sidan. Låt oss se vad som skulle hända om vi försökte skicka in nu:

$ git p4 submit -n
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would synchronize p4 checkout in /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would apply
  b4959b6 Trademark
  cbacd0a Table borders: yes please
  3be6fd8 Correct email address

Flaggan -n är en förkortning för --dry-run, som försöker rapportera vad som skulle hända om submit‑kommandot kördes på riktigt. I det här fallet ser det ut som att vi skulle skapa tre Perforce‑ändringsuppsättningar, som motsvarar de tre icke‑sammanfogade incheckningarna som ännu inte finns på Perforce‑servern. Det låter precis som vad vi vill, så låt oss se hur det går:

$ git p4 submit
[…]
$ git log --oneline --all --graph --decorate
* dadbd89 (HEAD, p4/master, p4/HEAD, master) Correct email address
* 1b79a80 Table borders: yes please
* 0097235 Trademark
* c4689fc Grammar fix
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Vår historik blev linjär, precis som om vi hade ombaserat innan vi skickade in (vilket faktiskt är exakt vad som hände). Det betyder att du kan skapa, arbeta på, kasta bort och sammanfoga grenar på Git‑sidan utan att oroa dig för att historiken på något sätt blir inkompatibel med Perforce. Om du kan ombasera det kan du bidra med det till en Perforce‑server.

Grenar

Om ditt Perforce‑projekt har flera grenar är du inte förlorad; git-p4 kan hantera det på ett sätt som får det att kännas som Git. Säg att ditt Perforce‑depot är upplagt så här:

//depot
  └── project
      ├── main
      └── dev

Och säg att du har en gren dev som har en view‑specifikation som ser ut så här:

//depot/project/main/... //depot/project/dev/...

Git-p4 kan automatiskt upptäcka den situationen och göra rätt:

$ git p4 clone --detect-branches //depot/project@all
Importing from //depot/project@all into project
Initialized empty Git repository in /private/tmp/project/.git/
Importing revision 20 (50%)
    Importing new branch project/dev

    Resuming with change 20
Importing revision 22 (100%)
Updated branches: main dev
$ cd project; git log --oneline --all --graph --decorate
* eae77ae (HEAD, p4/master, p4/HEAD, master) main
| * 10d55fb (p4/project/dev) dev
| * a43cfae Populate //depot/project/main/... //depot/project/dev/....
|/
* 2b83451 Project init

Notera specifikationen “@all” i depå‑sökvägen; den säger till git-p4 att klona inte bara den senaste ändringsuppsättningen för det underträdet, utan alla ändringsuppsättningar som någonsin har rört dessa sökvägar. Detta ligger närmare Gits klonbegrepp, men om du arbetar på ett projekt med lång historik kan det ta tid.

Flaggan --detect-branches säger åt git-p4 att använda Perforces gren‑specifikationer för att mappa grenar till Git‑referenser. Om dessa mappningar inte finns på Perforce‑servern (vilket är ett helt giltigt sätt att använda Perforce) kan du tala om för git-p4 vad grenmappningarna är, och då får du samma resultat:

$ git init project
Initialized empty Git repository in /tmp/project/.git/
$ cd project
$ git config git-p4.branchList main:dev
$ git clone --detect-branches //depot/project@all .

Att sätta konfigurationsvariabeln git-p4.branchList till main:dev talar om för git-p4 att “main” och “dev” båda är grenar, och att den andra är ett barn till den första.

Om vi nu kör git checkout -b dev p4/project/dev och gör några incheckningar är git-p4 smart nog att rikta in sig på rätt gren när vi kör git p4 submit. Tyvärr kan git-p4 inte blanda grunda kloner och flera grenar; om du har ett enormt projekt och vill arbeta på mer än en gren behöver du köra git p4 clone en gång per gren du vill skicka in till.

För att skapa eller integrera grenar måste du använda en Perforce‑klient. Git-p4 kan bara synka och skicka in till befintliga grenar, och det kan bara göra det en linjär ändringsuppsättning i taget. Om du sammanfogar två grenar i Git och försöker skicka in den nya ändringsuppsättningen kommer allt som registreras vara en bunt med filändringar; metadata om vilka grenar som ingår i integrationen går förlorad.

Sammanfattning om Git och Perforce

Git-p4 gör det möjligt att använda ett Git‑arbetsflöde med en Perforce‑server, och det fungerar ganska bra. Det är dock viktigt att komma ihåg att Perforce styr källan och att du bara använder Git för att arbeta lokalt. Var mycket försiktig med att dela Git‑incheckningar; om du har ett fjärrkodförråd som andra använder, skicka inte in några incheckningar som inte redan har skickats in till Perforce‑servern.

Om du vill blanda användningen av Perforce och Git fritt som klienter för versionshantering, och du kan övertyga serveradministratören att installera det, gör Git Fusion Git till en förstklassig versionshanteringsklient för en Perforce‑server.