Git
Chapters ▾ 2nd Edition

9.2 Git i inne systemy - Migracja do Gita

Migracja do Gita

Jeżeli masz obecny kod projektu w innym systemie kontroli wersji, ale zdecydowałeś się na używanie Gita, musisz w jakiś sposób go zmigrować. Ta sekcja przedstawia kilka importerów, które są dostarczane razem z Gitem dla najczęściej używanych systemów, a w dalszej części pokazuje jak stworzyć swój własny importer. Dowiesz się, jak importować dane z kilku największych, profesjonalnie używanych systemów kontroli wersji, ponieważ stanowią one źródło większości użytkowników, którzy zmieniają system, a także dlatego, że Git posiada dla nich dopracowane narzędzia.

Subversion

Jeżeli przeczytałeś poprzednią sekcję na temat używania git svn, możesz z łatwością użyć tamtych instrukcji aby sklonować za pomocą git svn clone repozytorium; następnie, przestań używać serwera Subversion, wypchaj zmiany do serwera Git i zacznij tylko na nim współpracować. Jeżeli potrzebujesz historii projektu, będziesz mógł to osiągnąć tak szybko, jak tylko możesz ściągnąć dana z serwera Subversion (co może chwilę zająć).

Niemniej, importowanie nie jest idealnym rozwiązaniem; a dlatego że zajmie to dużo czasu, powinieneś zrobić to raz a dobrze. Pierwszym problemem są informacje o autorze. W Subversion, każda osoba wgrywająca zmiany posiada konto systemowe na serwerze który zapisuje zmiany. Przykłady w poprzedniej sekcji, pokazują użytkownika schacon w kilku miejscach, takich jak wynik komendy blame czy git svn log. Jeżeli chciałbyś zamienić je na dane zgodne z Gitem, musisz stworzyć mapowania z użytkownika Subversion na autora w Git. Stwórz plik users.txt, który ma przypisane adresy w ten sposób:

schacon = Scott Chacon <schacon@geemail.com>
selse = Someo Nelse <selse@geemail.com>

Aby otrzymać listę autorów używanych przez SVN, uruchom komendę:

$ svn log --xml | grep author | sort -u | \
  perl -pe 's/.*>(.*?)<.*/$1 = /'

Generuje to wyjście dziennika w formacie XML, następnie zachowuje tylko linie z informacjami o autorze, odrzuca duplikaty i usuwa znaczniki XML. (Oczywiście działa to tylko na maszynie z zainstalowanymi programami grep, sort i perl). Następnie przekieruj wynik komendy do pliku users.txt, tak abyś mógł dodać odpowiednik użytkownika w Gitcie dla każdego wpisu.

Możesz przekazać ten plik do komendy git svn, aby pomóc jej lepiej zmapować dane przypisane do autorów. Możesz również wskazać git svn, aby nie zaciągał meta-danych, które normalnie Subversion importuje, poprzez dodanie opcji --no-metadata do komend clone lub init. Twoja komenda import wygląda więc tak:

$ git svn clone http://my-project.googlecode.com/svn/ \
      --authors-file=users.txt --no-metadata -s my_project

Teraz powinieneś mieć lepiej wyglądający projekt z Subversion w swoim katalogu my_project. Zamiast commitów, które wyglądają tak te:

commit 37efa680e8473b615de980fa935944215428a35a
Author: schacon <schacon@4c93b258-373f-11de-be05-5f7a86268029>
Date:   Sun May 3 00:12:22 2009 +0000

    fixed install - go to trunk

    git-svn-id: https://my-project.googlecode.com/svn/trunk@94 4c93b258-373f-11de-
    be05-5f7a86268029

masz takie:

commit 03a8785f44c8ea5cdb0e8834b7c8e6c469be2ff2
Author: Scott Chacon <schacon@geemail.com>
Date:   Sun May 3 00:12:22 2009 +0000

    fixed install - go to trunk

Nie tylko dane dotyczące autora ("Author") wyglądają lepiej, ale nie ma również znaczników git-svn-id.

Musisz jeszcze trochę posprzątać po imporcie. Na początek, powinieneś poprawić dziwne referencje które ustawił git svn. Najpierw przeniesiesz tagi, tak aby były normalnymi tagami, zamiast dziwnych zdalnych gałęzi, następnie przeniesiesz resztę gałęzi tak aby były lokalne.

Aby przenieść etykiety i zrobić z nich prawidłowe tagi Gita, uruchom:

$ cp -Rf .git/refs/remotes/origin/tags/* .git/refs/tags/
$ rm -Rf .git/refs/remotes/origin/tags

Pobierze to referencje które były zdalnymi gałęziami rozpoczynającymi się od remotes/origin/tags/ i zrobi z nich normalne (lekkie) etykiety.

Następnie, przenieś resztę referencji z refs/remotes, tak aby stały się lokalnymi gałęziami:

$ cp -Rf .git/refs/remotes/* .git/refs/heads/
$ rm -Rf .git/refs/remotes

Teraz wszystkie stare gałęzie są prawdziwymi gałęziami Gita, a stare tagi prawdziwymi tagami w Git. Ostatnią rzeczą do zrobienia jest dodanie nowego serwera Git jako zdalnego i wypchnięcie danych do niego. Poniżej znajduje się przykład dodania Twojego serwera jako zdalnego:

$ git remote add origin git@my-git-server:myrepository.git

Ponieważ chcesz aby wszystkie gałęzie i etykiety były na repozytorium, możesz uruchomić:

$ git push origin --all

Wszystkie gałęzie i tagi powinny być już na Twoim serwerze Gita, zaimportowane w czysty i zgrabny sposób.

Mercurial

Since Mercurial and Git have fairly similar models for representing versions, and since Git is a bit more flexible, converting a repository from Mercurial to Git is fairly straightforward, using a tool called "hg-fast-export", which you’ll need a copy of:

$ git clone http://repo.or.cz/r/fast-export.git /tmp/fast-export

The first step in the conversion is to get a full clone of the Mercurial repository you want to convert:

$ hg clone <remote repo URL> /tmp/hg-repo

The next step is to create an author mapping file. Mercurial is a bit more forgiving than Git for what it will put in the author field for changesets, so this is a good time to clean house. Generating this is a one-line command in a bash shell:

$ cd /tmp/hg-repo
$ hg log | grep user: | sort | uniq | sed 's/user: *//' > ../authors

This will take a few seconds, depending on how long your project’s history is, and afterwards the /tmp/authors file will look something like this:

bob
bob@localhost
bob <bob@company.com>
bob jones <bob <AT> company <DOT> com>
Bob Jones <bob@company.com>
Joe Smith <joe@company.com>

In this example, the same person (Bob) has created changesets under four different names, one of which actually looks correct, and one of which would be completely invalid for a Git commit. Hg-fast-export lets us fix this by adding ={new name and email address} at the end of every line we want to change, and removing the lines for any usernames that we want to leave alone. If all the usernames look fine, we won’t need this file at all. In this example, we want our file to look like this:

bob=Bob Jones <bob@company.com>
bob@localhost=Bob Jones <bob@company.com>
bob jones <bob <AT> company <DOT> com>=Bob Jones <bob@company.com>
bob <bob@company.com>=Bob Jones <bob@company.com>

The next step is to create our new Git repository, and run the export script:

$ git init /tmp/converted
$ cd /tmp/converted
$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors

The -r flag tells hg-fast-export where to find the Mercurial repository we want to convert, and the -A flag tells it where to find the author-mapping file. The script parses Mercurial changesets and converts them into a script for Git’s "fast-import" feature (which we’ll discuss in detail a bit later on). This takes a bit (though it’s much faster than it would be over the network), and the output is fairly verbose:

$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors
Loaded 4 authors
master: Exporting full revision 1/22208 with 13/0/0 added/changed/removed files
master: Exporting simple delta revision 2/22208 with 1/1/0 added/changed/removed files
master: Exporting simple delta revision 3/22208 with 0/1/0 added/changed/removed files
[…]
master: Exporting simple delta revision 22206/22208 with 0/4/0 added/changed/removed files
master: Exporting simple delta revision 22207/22208 with 0/2/0 added/changed/removed files
master: Exporting thorough delta revision 22208/22208 with 3/213/0 added/changed/removed files
Exporting tag [0.4c] at [hg r9] [git :10]
Exporting tag [0.4d] at [hg r16] [git :17]
[…]
Exporting tag [3.1-rc] at [hg r21926] [git :21927]
Exporting tag [3.1] at [hg r21973] [git :21974]
Issued 22315 commands
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects:     120000
Total objects:       115032 (    208171 duplicates                  )
      blobs  :        40504 (    205320 duplicates      26117 deltas of      39602 attempts)
      trees  :        52320 (      2851 duplicates      47467 deltas of      47599 attempts)
      commits:        22208 (         0 duplicates          0 deltas of          0 attempts)
      tags   :            0 (         0 duplicates          0 deltas of          0 attempts)
Total branches:         109 (         2 loads     )
      marks:        1048576 (     22208 unique    )
      atoms:           1952
Memory total:          7860 KiB
       pools:          2235 KiB
     objects:          5625 KiB
---------------------------------------------------------------------
pack_report: getpagesize()            =       4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit      = 8589934592
pack_report: pack_used_ctr            =      90430
pack_report: pack_mmap_calls          =      46771
pack_report: pack_open_windows        =          1 /          1
pack_report: pack_mapped              =  340852700 /  340852700
---------------------------------------------------------------------

$ git shortlog -sn
   369  Bob Jones
   365  Joe Smith

That’s pretty much all there is to it. All of the Mercurial tags have been converted to Git tags, and Mercurial branches and bookmarks have been converted to Git branches. Now you’re ready to push the repository up to its new server-side home:

$ git remote add origin git@my-git-server:myrepository.git
$ git push origin --all

Perforce

Następnym systemem, któremu się przyjrzymy z perspektywy importu, jest Perforce. Jak wspomnieliśmy powyżej, istnieją dwa sposoby, aby Git i Perforce mogły ze sobą rozmawiać: git-p4 oraz Perforce Git Fusion.

Perforce Git Fusion

Git Fusion czyni ten proces dość bezbolesnym. Wystarczy za pomocą pliku konfiguracyjnego skonfigurować ustawienia projektu, mapowania użytkowników i gałęzie (tak jak omówiono w Git Fusion), a następnie sklonować repozytorium. Git Fusion w wyniku daje Ci coś, co wygląda jak natywne repozytorium Git, które jest gotowe do wysłania do natywnego hosta Git. Możesz nawet użyć Perforce jako swojego hosta Git.

Git-p4

Git-p4 może również działać jako narzędzie do importowania. Jako przykład, zaimportujemy projekt Jam z Perforce Public Depot. Aby skonfigurować swojego klienta, musisz wyeksportować zmienną środowiskową P4PORT, aby wskazywała na magazyn Perforce:

$ export P4PORT=public.perforce.com:1666
Note

Aby móc kontynuować, będziesz potrzebował magazynu Perforce, z którym będziesz mógł się połączyć. Do naszych przykładów użyjemy publicznego magazynu pod adresem public.perforce.com, ale możesz użyć dowolnego magazynu, do którego masz dostęp.

Uruchom komendę git-p4 clone, aby zaimportować projekt Jam z serwera Perforce wskazując magazyn i ścieżkę projektu, oraz katalog do którego chcesz go zaimportować:

$ git-p4 clone //guest/perforce_software/jam@all p4import
Importing from //guest/perforce_software/jam@all into p4import
Initialized empty Git repository in /private/tmp/p4import/.git/
Import destination: refs/remotes/p4/master
Importing revision 9957 (100%)

Ten konkretny projekt ma tylko jedną gałąź, ale jeśli masz gałęzie, które są skonfigurowane z widokami gałęzi (lub po prostu zestawem katalogów), możesz użyć flagi --detect-branches do git p4 clone aby zaimportować wszystkie gałęzie projektu. Zajrzyj do Branching aby uzyskać więcej szczegółów na ten temat.

W tym momencie prawie zakończyłeś już pracę. Jeśli przejdziesz do katalogu p4import i uruchomisz git log, możesz zobaczyć zaimportowane dane:

$ git log -2
commit e5da1c909e5db3036475419f6379f2c73710c4e6
Author: giles <giles@giles@perforce.com>
Date:   Wed Feb 8 03:13:27 2012 -0800

    Correction to line 355; change </UL> to </OL>.

    [git-p4: depot-paths = "//public/jam/src/": change = 8068]

commit aa21359a0a135dda85c50a7f7cf249e4f7b8fd98
Author: kwirth <kwirth@perforce.com>
Date:   Tue Jul 7 01:35:51 2009 -0800

    Fix spelling error on Jam doc page (cummulative -> cumulative).

    [git-p4: depot-paths = "//public/jam/src/": change = 7304]

Możesz zauważyć, że każdy commit posiada identyfikator git-p4. Może on zostać, w razie gdybyś potrzebował dotrzeć do informacji o numerze zmiany zapisanym w Perforce. Jednak, gdybyś chciał usunąć ten identyfikator, teraz jest dobry moment aby to zrobić – przed wprowadzeniem jakichkolwiek zmian w nowym repozytorium. Możesz użyć git filter-branch aby usunąć wszystkie identyfikatory:

$ git filter-branch --msg-filter 'sed -e "/^\[git-p4:/d"'
Rewrite e5da1c909e5db3036475419f6379f2c73710c4e6 (125/125)
Ref 'refs/heads/master' was rewritten

Jeżeli uruchomisz git log, zobaczysz że wszystkie sumy SHA-1 dla commitów zostały zmienione i nie ma już identyfikatorów pozostawionych przez git-p4 w treściach komentarzy:

$ git log -2
commit b17341801ed838d97f7800a54a6f9b95750839b7
Author: giles <giles@giles@perforce.com>
Date:   Wed Feb 8 03:13:27 2012 -0800

    Correction to line 355; change </UL> to </OL>.

commit 3e68c2e26cd89cb983eb52c024ecdfba1d6b3fff
Author: kwirth <kwirth@perforce.com>
Date:   Tue Jul 7 01:35:51 2009 -0800

    Fix spelling error on Jam doc page (cummulative -> cumulative).

Twój kod jest teraz gotowy do wypchnięcia na nowy serwer Gita.

TFS

If your team is converting their source control from TFVC to Git, you’ll want the highest-fidelity conversion you can get. This means that, while we covered both git-tfs and git-tf for the interop section, we’ll only be covering git-tfs for this part, because git-tfs supports branches, and this is prohibitively difficult using git-tf.

Note

This is a one-way conversion. The resulting Git repository won’t be able to connect with the original TFVC project.

The first thing to do is map usernames. TFVC is fairly liberal with what goes into the author field for changesets, but Git wants a human-readable name and email address. You can get this information from the tf command-line client, like so:

PS> tf history $/myproject -recursive > AUTHORS_TMP

This grabs all of the changesets in the history of the project and put it in the AUTHORS_TMP file that we will process to extract the data of the User column (the 2nd one). Open the file and find at which characters start and end the column and replace, in the following command-line, the parameters 11-20 of the cut command with the ones found:

PS> cat AUTHORS_TMP | cut -b 11-20 | tail -n+3 | uniq | sort > AUTHORS

The cut command keeps only the characters between 11 and 20 from each line. The tail command skips the first two lines, which are field headers and ASCII-art underlines. The result of all of this is piped to uniq to eliminate duplicates, and saved to a file named AUTHORS. The next step is manual; in order for git-tfs to make effective use of this file, each line must be in this format:

DOMAIN\username = User Name <email@address.com>

The portion on the left is the “User” field from TFVC, and the portion on the right side of the equals sign is the user name that will be used for Git commits.

Once you have this file, the next thing to do is make a full clone of the TFVC project you’re interested in:

PS> git tfs clone --with-branches --authors=AUTHORS https://username.visualstudio.com/DefaultCollection $/project/Trunk project_git

Next you’ll want to clean the git-tfs-id sections from the bottom of the commit messages. The following command will do that:

PS> git filter-branch -f --msg-filter 'sed "s/^git-tfs-id:.*$//g"' -- --all

That uses the sed command from the Git-bash environment to replace any line starting with “git-tfs-id:” with emptiness, which Git will then ignore.

Once that’s all done, you’re ready to add a new remote, push all your branches up, and have your team start working from Git.

Własny skrypt importujący

Jeżeli Twój system kontroli wersji to nie jest żadnym ze wspomnianych wcześniej, powinieneś spojrzeć na importery dostępne w sieci – dobrej jakości importery dostępne są dla CVS, Clear Case, Visual Source Safe, a nawet zwykłego katalogu z archiwami. Jeżeli żadne z tych narzędzi nie zadziała, lub używasz mniej popularnego systemu, lub jeżeli potrzebujesz bardziej dostosowanego importu, powinieneś użyć git fast-import. Ta komenda odczytuje instrukcje przekazane na standardowe wejście programu i zapisuje dane w Git. Dużo łatwiej w ten sposób tworzyć obiekty Gita, niż uruchamiać jego niskopoziomowe komendy czy zapisywać surowe obiekty (por. Mechanizmy wewnętrzne w Git). W ten sposób możesz napisać skrypt importujący, który odczyta wszystkie potrzebne informacje z systemu z którego importujesz i wypisze instrukcje do wykonania na standardowe wyjście. Możesz następnie uruchomić ten program i przekazać wynik do git fast-import.

W celach demonstracyjnych, napiszesz prosty skrypt importujący. Załóżmy, że pracujesz na najnowszej kopii kodu źródłowego i wykonujesz czasami kopie zapasowe poprzez skopiowanie danych do katalogu z datą w formacie back_YYYY_MM_DD i chciałbyś je zaimportować do Gita. Twoja struktura katalogów wygląda następująco:

$ ls /opt/import_from
back_2014_01_02
back_2014_01_04
back_2014_01_14
back_2014_02_03
current

Aby zaimportować katalog do Gita, musisz przypomnieć sobie w jaki sposób Git przechowuje dane. Być może pamiętasz, Git z założenia jest zbiorem połączonych obiektów dotyczących commitów, które wskazują na ostatnią migawkę z zawartością. Wszystko co musisz zrobić, to wskazać fast-import jaka jest zawartość migawek, który commit na nie wskazuje, oraz kolejność w której występują. Twoją strategią będzie przejście kolejno przez wszystkie migawki, oraz stworzenie commitów z zawartością dla każdego z nich, łącząc każdy commit z poprzednim.

ak robiłeś już to w sekcji An Example Git-Enforced Policy, również napiszemy to w Ruby, ponieważ to na nim zazwyczaj pracuję, a jego kod jest dość czytelny. Możesz stworzyć ten przykład bardzo szybko, w praktycznie każdym innym języku który dobrze znasz – musi on wypisać na standardowe wyjście właściwe informacje. A jeżeli pracujesz na systemie Windows, będziesz musiał zwrócić szczególną uwagę, aby nie wprowadzić znaków powrotu karetki na końcach linii – git fast-import potrzebuje linie zakończone znakami nowej linii (LF), a nie powrotem karetki (CRLF), których używa Windows.

Aby rozpocząć, przejdziesz do docelowego katalogu i znajdziesz wszystkie podkatalogi, z których znajdują się migawki które chcesz zaimportować. Następnie wejdziesz do każdego podkatalogu i wypiszesz komendy konieczne do eksportu. Twoja pętla główna w programie wygląda tak:

last_mark = nil

# loop through the directories
Dir.chdir(ARGV[0]) do
  Dir.glob("*").each do |dir|
    next if File.file?(dir)

    # move into the target directory
    Dir.chdir(dir) do
      last_mark = print_export(dir, last_mark)
    end
  end
end

Uruchamiasz print_export w każdym katalogu, która przyjmuje jako parametry nazwę katalogu oraz znacznik poprzedniej migawki, a zwraca znacznik obecnej; w ten sposób możesz połączyć je poprawnie ze sobą. "Mark" jest terminem używanym przez fast-import, dla identyfikatora który przypisujesz do commita; podczas tworzenia kolejnych commitów, nadajesz każdemu z nich znacznik, który będzie użyty do połączenia go z innymi commitami. Dlatego pierwszą rzeczą którą robisz w metodzie print_export jest wygenerowanie znacznika pobranego z nazwy katalogu:

mark = convert_dir_to_mark(dir)

Zrobisz to poprzez wygenerowanie tablicy z nazwami katalogów, która używa jako indeksu znacznika będącego liczbą całkowitą. Twoja metoda wygląda więc tak:

$marks = []
def convert_dir_to_mark(dir)
  if !$marks.include?(dir)
    $marks << dir
  end
  ($marks.index(dir) + 1).to_s
end

Teraz, gdy masz już liczbę reprezentującą Twój commit, potrzebujesz daty do zamieszczenia w meta-danych commita. Ponieważ data jest użyta w nazwie katalogu, pobierzesz ją z nazwy. Następną linią w pliku print_export jest:

date = convert_dir_to_date(dir)

gdzie convert_dir_to_date jest zdefiniowane jako:

def convert_dir_to_date(dir)
  if dir == 'current'
    return Time.now().to_i
  else
    dir = dir.gsub('back_', '')
    (year, month, day) = dir.split('_')
    return Time.local(year, month, day).to_i
  end
end

Zwraca to liczbę całkowitą dla daty z katalogu. Ostatnią rzeczą potrzebną do zapisania meta-danych są informacje o osobie wprowadzającej zmiany, którą zapisujesz na stałe w zmiennej globalnej:

$author = 'John Doe <john@example.com>'

Teraz możesz rozpocząć wypisywanie danych dotyczących commitów dla swojego programu importującego. Początkowe informacje wskazują, że definiujesz nowy obiekt commit, oraz nazwę gałęzi do której będzie on przypisany, następnie podajesz znaczki który wygenerowałeś, informacje o osobie wprowadzającej zmiany oraz treść komentarza do zmiany, a na końcu poprzedni znacznik commita. Kod wygląda tak:

# print the import information
puts 'commit refs/heads/master'
puts 'mark :' + mark
puts "committer #{$author} #{date} -0700"
export_data('imported from ' + dir)
puts 'from :' + last_mark if last_mark

Wpisujesz na sztywno strefę czasową (-0700), ponieważ jest to najprostsze podejście. Jeżeli importujesz z innego systemu, musisz wskazać strefę czasową jako przesunięcie (ang. offset). Treść komentarza do zmiany musi być wyrażona w specjalnym formacie:

data (size)\n(contents)

Format składa się z słowa kluczowego data, długości danych do wczytania, znaku nowej linii, oraz na końcu samych danych. Ponieważ musisz używać tego samego formatu, do przekazania zawartości plików w dalszych etapach, stwórz metodę pomocniczą, export_data:

def export_data(string)
  print "data #{string.size}\n#{string}"
end

Jedyne co pozostało, to wskazanie zawartości pliku dla każdej migawki. Jest to proste, ponieważ masz wszystkie pliki w katalogu – możesz wypisać komendę deleteall, a następnie zawartość wszystkich plików w katalogu. Następnie Git zapisze każdą migawkę:

puts 'deleteall'
Dir.glob("**/*").each do |file|
  next if !File.file?(file)
  inline_data(file)
end

Uwaga: Ponieważ spora część systemów kontroli wersji myśli o kolejnych rewizjach jako o zmianach z jednego commita do drugiego, fast-import może również pobrać komendy dla każdego commita, w których można wskazać jakie pliki zostały dodane, usunięte, lub zmodyfikowane i jaka jest ich nowa zawartość. Mógłbyś obliczyć różnice między migawkami i dostarczyć tylko te dane, ale działanie w ten sposób jest bardziej skomplikowane – łatwiej wskazać Gitowi wszystkie dane, a on sam się zajmie obliczaniem różnic. Jeżeli jednak uważasz, że ten sposób jest bardziej dopasowany do danych które posiadasz, sprawdź podręcznik systemowy dla komendy fast-import, aby dowiedzieć się w jaki sposób przekazać jej dane.

Format przekazywania zawartości nowego pliku lub wskazywania zmodyfikowanego z nową zawartością jest następujący:

M 644 inline path/to/file
data (size)
(file contents)

W tym przykładzie, 644 oznacza uprawnienia do pliku (jeżeli masz pliki wykonywalne, musisz wskazać 755), a inline mówi o tym, że będziesz przekazywał dane zaraz po tej linii. Twoja metoda inline_data wygląda tak:

def inline_data(file, code = 'M', mode = '644')
  content = File.read(file)
  puts "#{code} #{mode} inline #{file}"
  export_data(content)
end

Używasz ponownie metody export_data, którą zdefiniowałeś wcześniej, ponieważ działa to tak samo jak podczas wskazywania treści komentarza do commita.

Ostatnią rzeczą, którą musisz zrobić, jest zwrócenie bieżącego znaku, aby można go było przekazać do następnej iteracji:

return mark
Note

Jeżeli pracujesz na systemie Windows, musisz upewnić się, że dodajesz jeszcze jeden krok. Jak wspomniałem wcześniej, system Windows używa znaków CRLF jak znaczników końca linii, a git fast-import oczekuje tylko LF. Aby obejść ten problem i uszczęśliwić git fast-import, musisz wskazać ruby, aby używał znaków LF zamiast CRLF:

$stdout.binmode

Tylko tyle. Oto cały skrypt:

#!/usr/bin/env ruby

$stdout.binmode
$author = "John Doe <john@example.com>"

$marks = []
def convert_dir_to_mark(dir)
    if !$marks.include?(dir)
        $marks << dir
    end
    ($marks.index(dir)+1).to_s
end


def convert_dir_to_date(dir)
    if dir == 'current'
        return Time.now().to_i
    else
        dir = dir.gsub('back_', '')
        (year, month, day) = dir.split('_')
        return Time.local(year, month, day).to_i
    end
end

def export_data(string)
    print "data #{string.size}\n#{string}"
end

def inline_data(file, code='M', mode='644')
    content = File.read(file)
    puts "#{code} #{mode} inline #{file}"
    export_data(content)
end

def print_export(dir, last_mark)
    date = convert_dir_to_date(dir)
    mark = convert_dir_to_mark(dir)

    puts 'commit refs/heads/master'
    puts "mark :#{mark}"
    puts "committer #{$author} #{date} -0700"
    export_data("imported from #{dir}")
    puts "from :#{last_mark}" if last_mark

    puts 'deleteall'
    Dir.glob("**/*").each do |file|
        next if !File.file?(file)
        inline_data(file)
    end
    mark
end


# Loop through the directories
last_mark = nil
Dir.chdir(ARGV[0]) do
    Dir.glob("*").each do |dir|
        next if File.file?(dir)

        # move into the target directory
        Dir.chdir(dir) do
            last_mark = print_export(dir, last_mark)
        end
    end
end

Jeżeli uruchomisz ten skrypt, otrzymasz wynik podobny do tego:

$ ruby import.rb /opt/import_from
commit refs/heads/master
mark :1
committer John Doe <john@example.com> 1388649600 -0700
data 29
imported from back_2014_01_02deleteall
M 644 inline README.md
data 28
# Hello

This is my readme.
commit refs/heads/master
mark :2
committer John Doe <john@example.com> 1388822400 -0700
data 29
imported from back_2014_01_04from :1
deleteall
M 644 inline main.rb
data 34
#!/bin/env ruby

puts "Hey there"
M 644 inline README.md
(...)

Aby uruchomić importer, przekaż wynik do git fast-import będąc w katalogu z repozytorium Gita do którego chcesz zaimportować dane. Możesz stworzyć nowy katalog, następnie uruchomić git init w nim, a potem uruchomić skrypt:

$ git init
Initialized empty Git repository in /opt/import_to/.git/
$ ruby import.rb /opt/import_from | git fast-import
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects:       5000
Total objects:           13 (         6 duplicates                  )
      blobs  :            5 (         4 duplicates          3 deltas of          5 attempts)
      trees  :            4 (         1 duplicates          0 deltas of          4 attempts)
      commits:            4 (         1 duplicates          0 deltas of          0 attempts)
      tags   :            0 (         0 duplicates          0 deltas of          0 attempts)
Total branches:           1 (         1 loads     )
      marks:           1024 (         5 unique    )
      atoms:              2
Memory total:          2344 KiB
       pools:          2110 KiB
     objects:           234 KiB
---------------------------------------------------------------------
pack_report: getpagesize()            =       4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit      = 8589934592
pack_report: pack_used_ctr            =         10
pack_report: pack_mmap_calls          =          5
pack_report: pack_open_windows        =          2 /          2
pack_report: pack_mapped              =       1457 /       1457
---------------------------------------------------------------------

Jak widzisz, gdy zakończy się powodzeniem, pokaże Ci trochę statystyk na temat tego co zdziałał. W tym przypadku, zaimportowałeś do jednej gałęzi łącznie 13 obiektów z 4 commitów. Teraz możesz uruchomić git log, aby zobaczyć swoją nową historię projektu:

$ git log -2
commit 3caa046d4aac682a55867132ccdfbe0d3fdee498
Author: John Doe <john@example.com>
Date:   Tue Jul 29 19:39:04 2014 -0700

    imported from current

commit 4afc2b945d0d3c8cd00556fbe2e8224569dc9def
Author: John Doe <john@example.com>
Date:   Mon Feb 3 01:00:00 2014 -0700

    imported from back_2014_02_03

Proszę bardzo – ładne, czyste repozytorium Git. Warto zauważyć, że żadne dane nie zostały pobrane – nie masz żadnych plików w swoim katalogu roboczym Aby je pobrać, musisz wykonać reset do momentu, w którym teraz jest master:

$ ls
$ git reset --hard master
HEAD is now at 3caa046 imported from current
$ ls
README.md main.rb

Możesz zrobić dużo więcej przy pomocy narzędzia fast-import – obsłużyć różne tryby, dane binarne, gałęzie i ich łączenie, etykiety, wskaźniki postępu i inne. Wiele przykładów o bardziej skomplikowanych scenariuszach działania jest dostępnych w katalogu contrib/fast-import w kodzie źródłowym Gita.

scroll-to-top