Skip to content

Latest commit

 

History

History
374 lines (308 loc) · 14.2 KB

import-custom.asc

File metadata and controls

374 lines (308 loc) · 14.2 KB

Un importateur personnalisé

Si votre système n’est pas un de ceux ci-dessus, vous devriez chercher un importateur en ligne – des importateurs de qualité sont disponibles pour plein d’autres systèmes, incluant CVS, Clear Case, Visual Source Safe, même un dossier d’archives. Si aucun de ces outils ne fonctionne pour vous, vous avez un outil plus obscur, ou alors vous avez besoin d’un procédé d’importation personnalisé, vous devriez utiliser git fast-import. Cette commande lit des instructions simples depuis l’entrée standard pour écrire des données Git spécifiques. Il est bien plus facile de créer des objets Git de cette façon que de lancer des commandes Git brutes ou que d’essayer d’écrire les objets bruts (voir ch10-git-internals.asc pour plus d’informations). De cette façon, vous pouvez écrire un script d’importation qui lit l’information nécessaire hors du système duquel vous importez et qui affiche les instructions directement dans la sortie standard. Vous pouvez alors lancer ce programme et envoyer sa sortie à travers un tube dans git fast-import.

Pour démontrer rapidement, vous écrirez un importateur simple. Supposez que vous travaillez dans current, vous sauvegardez votre projet en copiant occasionnellement le dossier dans un dossier de sauvegarde estampillé de la date back_YYYY_MM_DD, et vous voulez importer cela dans Git. Votre structure de dossier ressemble à ceci :

$ ls /opt/import_from
back_2014_01_02
back_2014_01_04
back_2014_01_14
back_2014_02_03
current

Pour importer un dossier Git, vous devez passer en revue comment Git stocke ses données. Comme vous vous le rappelez, Git est fondamentalement une liste liée d’objets commit qui pointent sur un instantané de contenu. Tout ce que vous avez à faire est de dire à fast-import ce que sont les instantanés de contenu, quelles données de commit pointent sur eux, et l’ordre dans lequel ils vont. Votre stratégie sera d’explorer les instantanés un à un et créer les commits avec les contenus dans chaque dossier, en liant chaque commit avec le précédent.

Comme nous l’avons fait dans ch08-customizing-git.asc, nous écrirons ceci en Ruby, parce que c’est ce avec quoi nous travaillons généralement et ça a tendance à être facile à lire. Vous pouvez écrire cet exemple assez facilement avec n’importe quel langage de programmation auquel vous êtes familier – il faut seulement afficher l’information appropriée dans stdout. Et, si vous travaillez sous Windows, cela signifie que vous devrez prendre un soin particulier à ne pas introduire de retour chariot (carriage return, CR) à la fin de vos lignes – git fast-import est très exigeant ; il accepte seulement la fin de ligne (Line Feed, LF) et pas le retour chariot fin de ligne (CRLF) que Windows utilise.

Pour commencer, vous vous placerez dans le dossier cible et identifierez chaque sous-dossier, chacun étant un instantané que vous voulez importer en tant que commit. Vous vous placerez dans chaque sous-dossier et afficherez les commandes nécessaires pour l’exporter. Votre boucle basique principale ressemble à ceci :

last_mark = nil

# boucle sur les dossiers
Dir.chdir(ARGV[0]) do
  Dir.glob("*").each do |dir|
    next if File.file?(dir)

    # rentre dans chaque dossier cible
    Dir.chdir(dir) do
      last_mark = print_export(dir, last_mark)
    end
  end
end

Vous lancez print_export à l’intérieur de chaque dossier, qui prend le manifeste et la marque de l’instantané précédent et retourne la marque et l’empreinte de celui-ci ; de cette façon, vous pouvez les lier proprement. `Marque'' est le terme de `fast-import pour un identifiant que vous donnez à un commit ; au fur et à mesure que vous créez des commits, vous donnez à chacun une marque que vous pouvez utiliser pour le lier aux autres commits. Donc, la première chose à faire dans votre méthode print_export est de générer une marque à partir du nom du dossier :

mark = convert_dir_to_mark(dir)

Vous ferez ceci en créant un tableau de dossiers et en utilisant la valeur d’index comme marque, car une marque doit être un nombre entier. Votre méthode ressemble à ceci :

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

Maintenant que vous avez une représentation par un entier de votre commit, vous avez besoin d’une date pour les métadonnées du commit. Puisque la date est exprimée dans le nom du dossier, vous l’analyserez. La ligne suivante dans votre fichier print_export est

date = convert_dir_to_date(dir)

convert_dir_to_date est définie comme

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

Cela retourne une valeur entière pour la date de chaque dossier. Le dernier bout de méta-information dont vous avez besoin pour chaque commit est la donnée de l’auteur, que vous codez en dur dans une variable globale :

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

Maintenant vous êtes prêt à commencer à publier l’information de commit pour votre importateur. L’information initiale déclare que vous êtes en train de définir un objet commit et sur quelle branche il est, suivi de la marque que vous avez générée, l’information d’auteur et le message de commit, et ensuite le précédent commit, s’il y en a un. Le code ressemble à ceci :

# affiche l'information d'import
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

Vous codez en dur le fuseau horaire (-0700) parce que c’est facile de faire ainsi. Si vous importez depuis un autre système, vous devez spécifier le fuseau horaire comme décalage. Le message de commit doit être exprimé dans un format spécial :

data (taille)\n(contenu)

Le format est constitué du mot data, de la taille de la donnée à lire, d’une nouvelle ligne et finalement de la donnée. Comme vous avez besoin d’utiliser le même format pour spécifier le contenu du fichier plus tard, vous créez une méthode assistante, export_data :

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

Tout ce qui reste à faire est de spécifier le contenu du fichier pour chaque instantané. C’est facile, car vous les avez dans un dossier – vous pouvez imprimer la commande deleteall suivie par le contenu de chaque fichier du dossier. Git enregistrera ensuite chaque instantané de manière appropriée :

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

Note : Comme beaucoup de systèmes conçoivent leurs révisions comme des changements d’un commit à l’autre, fast-import peut aussi prendre des commandes avec chaque commit pour spécifier quels fichiers ont été ajoutés, supprimés ou modifiés et ce qu’est le nouveau contenu. Vous pourriez calculer les différences entre instantanés et fournir seulement cette donnée, mais faire ainsi est plus complexe – vous pouvez aussi bien donner à Git toutes les données et le laisser faire. Si cela convient mieux pour vos données, référez-vous à la page de manuel fast-import pour les détails sur la manière de fournir les données de cette façon.

Le format pour lister le contenu d’un nouveau fichier ou pour spécifier un fichier modifié avec le nouveau contenu est le suivant :

M 644 inline path/to/file
data (taille)
(contenu du fichier)

Ici, 644 est le mode (si vous avez des fichiers exécutables, vous devez le détecter et spécifier 755 à la place), et inline dit que vous listerez le contenu immédiatement après cette ligne. Votre méthode inline_data ressemble à ceci :

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

Vous réutilisez la méthode export_data que vous avez définie plus tôt, parce que c’est de la même façon que vous avez spécifié vos données du message de commit.

La dernière chose que vous avez besoin de faire est de retourner la marque courante pour qu’elle soit passée à la prochaine itération :

return mark
Note

Si vous êtes sous Windows, vous devrez vous assurer d’ajouter une étape supplémentaire. Comme mentionné précédemment, Windows utilise CRLF comme caractères de fin de ligne alors que git fast-import ne s’attend qu’à LF. Pour contourner ce problème et satisfaire git fast-import, vous devez indiquer à Ruby d’utiliser LF au lieu de CRLF :

$stdout.binmode

Et voilà. Voici le script dans son intégralité :

#!/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


# explore les dossiers
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

Si vous lancez ce script, vous obtiendrez un contenu qui ressemble à peu près à ceci :

$ 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
(...)

Pour lancer l’importateur, envoyez à travers un tube cette sortie à git fast-import pendant que vous êtes dans le dossier Git dans lequel vous voulez importer. Vous pouvez créer un nouveau dossier et ensuite exécuter git init à l’intérieur de celui-ci comme point de départ, et ensuite exécuter votre script :

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

Comme vous pouvez le voir, lorsque c’est terminé avec succès, il vous donne un lot de statistiques sur ce qu’il a fait. Dans ce cas-ci, vous avez importé un total de 13 objets pour 4 commits dans une branche. Maintenant, vous pouvez lancer git log pour voir votre nouvel historique :

$ 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

Vous y voilà — un dépôt Git beau et propre. Il est important de noter que rien n’est extrait – vous n’avez d’abord aucun fichier dans votre répertoire de travail. Pour les obtenir, vous devez réinitialiser votre branche là où master est maintenant :

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

Vous pouvez faire beaucoup plus avec l’outil fast-import – manipuler différents modes, les données binaires, les branches multiples et la fusion, les étiquettes, les indicateurs de progression, et plus encore. Nombre d’exemples de scénarios plus complexes sont disponibles dans le dossier contrib/fast-import du code source Git.