Skip to content

Latest commit

 

History

History
376 lines (311 loc) · 13.6 KB

import-custom.asc

File metadata and controls

376 lines (311 loc) · 13.6 KB

Importeren op maat

Als jouw systeem niet een van de bovenstaande is, moet je op het internet gaan zoeken naar een importeerder - goede importeerders zijn beschikbaar voor vele andere systemen, inclusief CVS, Clear Case, Visual Source Safe en zelfs een directory met archieven. Als geen van die tools voor jou geschikt zijn, je hebt een heel obscure tool, of je hebt om een andere reden een meer aangepaste importeer proces nodig, dan moet je git fast-import gebruiken. Dit commando leest eenvoudige instructies van stdin om specifieke Git gegevens te schrijven. Het is veel eenvoudiger om op deze manier Git objecten aan te maken dan de rauwe Git commando’s te gebruiken of om zelf de rauwe objecten te schrijven (zie ch10-git-internals.asc voor meer informatie). Op deze manier kan je een import script schrijven die de benodigde informatie uit het te importeren systeem leest en op stdout eenvoudige instructies afdrukt. Je kunt dit programma dan aanroepen en de uitvoer doorgeven aan git fast-import met een pipe-instructie.

Om een snelle demonstratie te geven, ga je een eenvoudige importeerder schrijven. Stel dat je in current aan het werk bent, je maakt op gezette tijden een backup van je project door de directory naar een andere te kopieren met een datum-tijd indicatie genaamd back_YYYY_MM_DD, en je wilt dit importeren in Git. Je directory-structuur ziet er zo uit:

$ ls /opt/import_from
back_2014_01_02
back_2014_01_04
back_2014_01_14
back_2014_02_03
current

Om een Git directory te importeren, moet je weten hoe Git haar gegevens opslaat. Zoals je je zult herinneren, is Git in de basis een geschakelde lijst van commit objecten die verwijzen naar een snapshot van de inhoud. Alles wat je hoeft te doen is fast-import te vertellen wat de snapshots van de inhoud zijn, welke commit-gegevens hiernaar verwijzen en de volgorde waarin ze staan. Je strategie zal zijn om een voor een door de snapshots te gaan en commits te maken met de inhoud van elke directory, en elke commit terug te laten verwijzen naar de vorige.

Zoals we in ch08-customizing-git.asc gedaan hebben, schrijven we dit in Ruby, omdat dit is waar we gewoonlijk mee werken en het redelijk eenvoudig te lezen is. Je kunt dit voorbeeld redelijk eenvoudig in iets schrijven waar je bekend mee bent - het moet gewoon de juiste informatie naar stdout uitvoeren. En als je op Windows draait, betekent dit dat je ervoor moet zorgen dat je geen carriage returns aan het eind van je regels gebruikt - git fast-import is erg secuur in het alleen accepteren van line feeds (LF) niet de carriage return line feeds (CFLF) die door Windows wordt gebruikt.

Om te beginnen, spring je in de doel directory en identificeert elke subdirectory, die elk een snapshot is van wat je wilt importeren als een commit. Je springt in elke subdirectory en drukt de commando’s af die nodig zijn om het te exporteren. Je hoofdlus ziet er zo uit:

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

Je roept print_export in elke directory aan, die de inhoud en kenmerk (mark) van de vorige snapshot neemt en je de inhoud en kenmerk van deze teruggeeft; op die manier kan je ze goed koppelen.

`Mark'' is de term die `fast-import gebruikt voor een identificatie die je aan een commit geeft; tijdens het aanmaken van commit geef je elk een kenmerk die je kunt gebruiken om als koppeling vanaf andere commits. Dus, het eerste wat je moet doen in je print_export methode is een kenmerk genereren van de naam van de directory:

mark = convert_dir_to_mark(dir)

Je doet dit door een reeks (array) directories te maken en de indexwaarde als het kenmerk te gebruiken, omdat een kenmerk een integer dient te zijn. Je methode ziet er zo uit:

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

Nu je een integer representatie hebt van je commit, heb je een datum nodig voor de commit metadata. Omdat de datum in de naam van de directory zit, ga je het eruit halen. De volgende regel in je print_export bestand is:

date = convert_dir_to_date(dir)

where convert_dir_to_date is defined as:

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

Dat retourneert een integerwaarde voor de datum van elke directory. Het laatste stukje meta-informatie dat je nodig hebt voor elke commit zijn de gegevens van de committer, die je hardgecodeerd hebt in een globale variabele:

$author = 'John Doe <[email protected]>'

Nu ben je klaar om de commit data af te drukken voor je importeerder. De initiële informatie geeft aan dat je een commit object definieert en op welke branch deze staat, gevolgd door het kenmerk die je hebt gegenereert, de informatie van de committer en het commit bericht, en dan de vorige commit als die er is. De code ziet er zo uit:

# 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

Je geeft de tijdzone (-0700) hardgecodeerd, omdat dit nu eenmaal makkelijk is. Als je vanuit een ander systeem importeert, moet je de tijdzone als een relatieve verschuiving (offset) weergeven. Het commit bericht moet worden uitgedrukt in een speciaal formaat:

data (size)\n(contents)

Het formaat bestaat uit het woord data, de grootte van de te lezen gegevens, een nieuwe regel en tot slot de gegevens. Omdat je hetzelfde formaat later nodig hebt om de bestandsinhoud te specificeren, maak je een hulpmethode, export_data:

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

Wat er nu nog overblijft is het specifieren van de bestandsinhoud voor elk snapshot. Dit is makkelijk, omdat je elk van deze in een directory hebt - je kunt het deleteall commando afdrukken gevolgd door de inhoud van elk bestand in de directory. Git zal dan elke snapshot op de juiste manier opslaan:

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

Let op: Omdat veel systemen hun revisies zien als wijzigingen van de ene commit naar de ander, kan fast-import ook commando’s accepteren waar bij elke commit kan worden aangegeven welke bestanden er zijn toegevoegd, verwijderd of gewijzigd en welke nieuwe elementen erbij zijn gekomen. Je kunt de verschillen tussen de snapshots berekenen en alleen deze gegevens meegeven, maar het is veel ingewikkelder om dit te doen - je kunt net zo makkelijk Git alle gegevens meegeven en het hem laten uitzoeken. Als dit beter past bij jouw gegevens, neem dan de fast-import man page door voor de details hoe je deze gegevens moet doorgeven in dat geval.

Het formaat om de nieuwe bestandsinhoud weer te geven of om een gewijzigd bestand te specificeren met de nieuwe inhoud is als volgt:

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

Hier is 644 de mode (als je aanroepbare bestanden hebt, moet je dit detecteren en in plaats daarvan 755 opgeven), en in de code (inline) geef je de inhoud direct achter deze regel weer. Je inline_data methode ziet er zo uit:

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

Je hergebruikt de export_data methode die je eerder hebt gedefinieerd, omdat dit formaat hetzelfde is als waarmee je de commit bericht gegevens specificeert.

Het laatste wat je nog moet doen is om het huidige kenmerk te retourneren, zodat het kan worden doorgegeven in de volgende iteratie:

return mark
Note

Als je op Windows draait moet je je ervan verzekeren dat je een extra stap toevoegt. Zoals eerder opgemerkt, gebruikt Windows CRLF voor nieuwe regel tekens waar git fast-import alleen LF verwacht. Om dit probleem te verhelpen en git fast-import blij te maken, moet je ruby vertellen om LF te gebruiken in plaats van CRLF:

$stdout.binmode

Dat is 't. Hier is het script in zijn totaliteit:

#!/usr/bin/env ruby

$stdout.binmode
$author = "John Doe <[email protected]>"

$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

Als je dit script aanroept, krijg je inhoud dat er ongeveer zo uit ziet:

$ ruby import.rb /opt/import_from
commit refs/heads/master
mark :1
committer John Doe <[email protected]> 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 <[email protected]> 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
(...)

Om de importeerder te laten draaien, geeft je de uitvoer met een pipe door aan git fast-import terwjil je in de Git directory staat waar je naartoe wilt importeren. Je kunt een nieuwe directory aanmaken en daarna git init daarin aanroepen als een begin, en daarna je script aanroepen:

$ 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
---------------------------------------------------------------------

Zoals je kunt zien, als het succesvol eindigt, geeft het je een bergje statistieken wat het allemaal heeft bereikt. In dit geval, heb je in totaal 13 objecten geïmporteerd voor 4 commits in 1 branch. Je kunt nu git log aanroepen om je nieuwe historie te bekijken:

$ git log -2
commit 3caa046d4aac682a55867132ccdfbe0d3fdee498
Author: John Doe <[email protected]>
Date:   Tue Jul 29 19:39:04 2014 -0700

    imported from current

commit 4afc2b945d0d3c8cd00556fbe2e8224569dc9def
Author: John Doe <[email protected]>
Date:   Mon Feb 3 01:00:00 2014 -0700

    imported from back_2014_02_03

Kijk eens aan - een mooie, schone Git repository. Het is belangrijk om op te merken dat er niets is uitgecheckt - je hebt initieel nog geen enkel bestand in je werk directory. Om deze te krijgen, moet je je branch resetten tot waar master nu is:

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

Je kunt nog veel meer doen met het fast-import gereedschap - verschillende modes behandelen, binaire gegevens, meerdere branches en mergen, tags, voortgangs-indicators en meer. Een aantal voorbeelden van meer ingewikkelde scenarios zijn beschikbaar in de contrib/fast-import directory van de Git broncode.