Git
Chapters ▾ 2nd Edition

9.2 Ang Git at iba pang mga Sistema - Paglilipat sa Git

Paglilipat sa Git

Kung mayroon kang isang umiiral na codebase sa ibang VCS ngunit ikaw ay nakapagpasyang magsimulang gumamit ng Git, kailangan mong ilipat ang iyong proyekto sa isang paraan o iba. Ang seksyong ito ay dadako sa iilang mga importer para sa karaniwang mga sistema, at pagkatapos ay ipapakita kung paano i-develop ang iyong sariling pasadya na importer. Matututo ka kung paano mag-import ng datos mula sa ilan sa mas malalaking propesyonal na ginagamit na SCM na mga sistema, dahil binubuo nila ang karamihan sa mga gumagamit na nagpapalit, at dahil sa mataas na kalidad na mga kasangkapan para sa kanilay na madaling makararating.

Subversion

Kung nabasa mo ang nakaraang seksyon tungkol sa paggamit ng git svn, maaari mong madaling gamitin ang mga instruksyon sa git svn clone ng isang repositoryo; pagkatapos, huminto sa paggamit ng Subversion na server, mag-push sa isang bagong Git na server, at simulan ang paggamit nito. Kung gusto mo ang kasaysayan, makukuha mo ito nang mabilisan dahil maaari kang mag-pull ng datos palabas sa Subversion na server (na maaaring magtagal).

Samantala, ang import ay hindi perpekto; at dahil ito ay lilipas ng matagal, mangyaring gawin mo ito nang tama. Ang unang problema ay ang impormasyon ng may-akda. Sa Subversion, bawat tao na nag-commit ay may isang user sa sistema na nakatalaga sa commit na impormasyon. Ang mga halimbawa sa nakaraang seksyon ay nagpapakita ng schacon sa ilang mga lugar, kagaya ng blame na output at ang git svn log. Kung gusto mong mag-map nito sa mas mabuting datos ng Git na may-akda, kailangan mo ng isang pag-map mula sa Subversion na mga user sa mga Git na may-akda. Gumawa ng isang file na tinatawag na users.txt na mayroon nitong pag-map sa isang format katulad nito:

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

Upang makakuha ng isang listahan ng mga pangalan ng may-akda na ginagamit ng SVN, maaari mong patakbuhin ito:

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

Iyon ay maglilikha ng log na output na nasa XML na format, at papanatilihin lamang ang mga linya na may impormasyon ng may-akda, tatanggalin ang mga magkakapareho, buburahin ang mga tag na XML. (Halatang ito ay gumagana lamang sa isang makina na may grep, sort, at perl na naka-install.) Pagkatapos, ipasa ang output na iyon sa iyong users.txt na file upang maaari kang magdagdag ng katumbas na datos ng Git na user na kasunod sa bawat entrada.

Maaari mong ibigay ang file na ito sa git svn upang tulungan itong i-map ang may-akda na datos nang mas wasto. Maaari mo ring sabihan ang git svn na huwag ilakip ang metadata na normal na ini-import ng Subversion, sa pamamagitan ng pagpasa ng --no-metadata sa ` clone o init na utos (bagaman kung gusto mong panatilihin ang synchronisation-metadata, walang anumang iwan ang parameter na ito). Ginagawa nitong mukhang ganito ang iyong import na utos:

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

Ngayon dapat kang magkaroon ng isang mas magandang Subverion na import sa iyong my_project na direktoryo. Sa halip ng mga commit na nagmumukhang katulad nito

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

magmumukha silang katulad nito:

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

    fixed install - go to trunk

Hindi lang nagmumukhang mas maayos ang May-akda na field, subalit ang git-svn-id ay wala na rin doon.

Kailangan mo ring gumawa ng kakaunting paglilinis sa post-import. Para sa isang bagay, dapat mong linisin ang kakaibang mga reperensya na itinakda ng git svn. Una ililipat mo ang mga tag upang sila ay maging aktwal na mga tag sa halip na kakaibang remote na mga branch, at pagkatapos ay ililipat mo ang natitirang mga branch upang sila ay maging lokal.

Upang maglipat ng mga tag upang maging nararapat na Git na mga tag, patakbuhin ang:

$ for t in $(git for-each-ref --format='%(refname:short)' refs/remotes/tags); do git tag ${t/tags\//} $t && git branch -D -r $t; done

Kukunin nito ang mga reperensya na remote na mga branch na nagsisimula sa refs/remotes/tags/ at gagawin silang tunay (magaan) na mga tag.

Susunod, ilipat ang natitira sa mga reperensya sa ilalim ng refs/remotes upang maging lokal ng mga branch:

$ for b in $(git for-each-ref --format='%(refname:short)' refs/remotes); do git branch $b refs/remotes/$b && git branch -D -r $b; done

Maaaring mangyari na makikita mo ang ilang karagdagang mga branch na naka-suffix ng @xxx (kung saan ang xxx ay isang numero), habang sa Subversion makakakita ka lamang ng isang branch. Ito ay isang aktwal na tampok ng Subversion na tinatawag na “peg-revisions”, na isang bagay na simpleng walang sintaktikal na katumbas sa Git. Kaya, ang git svn ay simpleng nagdaragdag ng numero ng svn na bersyon sa pangalan ng branch sa parehong paraan lamang katulad ng pagsulat mo nito sa svn upang isalaysay ang peg-revision sa branch na iyon. Kung wala ka nang pakialam tungkol sa mga peg-revision, simpleng burahin ang mga iyon:

$ for p in $(git for-each-ref --format='%(refname:short)' | grep @); do git branch -D $p; done

Ngayon lahat ng lumang mga branch ay tunay na Git na mga branch at lahat ng lumang mga tag ay tunay na Git na mga tag.

Mayroong isang huling bagay na dapat linisin. Sa kasamaang palad, ang git svn ay gumagawa ng isang karagdag na branch na nakapangalang trunk, na nagma-map sa default na branch ng Subversion, ngunit ang trunk na ref ay tumuturo sa parehong lugar ng master. Dahil ang master ay mas kawikaing Git, narito ang kung papaano magtanggal ng karagdagang branch:

$ git branch -d trunk

Ang huling bagay na gagawin ay ang pagdagdag ng iyong bagong Git na server bilang isang remote at mag-push dito. Narito ang isang halimbawa ng pagdaragdag ng iyong server bilang isang remote:

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

Dahil gusto mong lahat ng iyong mga branch at mga tag na pumunta paitaas, maaari mo nang patakbuhin ito:

$ git push origin --all
$ git push origin --tags

Ang lahat ng iyong mga branch at tag ay dapat nasa iyong bagong Git na server na nasa isang maganda, malinis na import.

Mercurial

Dahil ang Mercurial at Git ay patas na magkaparehong mga modelo para sa pag-representa ng mga bersyon, at dahil ang Git ay medyo mas umaangkop, ang pagpalit ng isang repositoryo mula sa Mercurial patungo sa Git ay patas na wagas, ang paggamit ng isang kasangkapan na tinatawag na "hg-fast-export", na kakailanganin mo ng isang kopya:

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

Ang unang hakbang sa pagpapalit ay ang pagkuha ng isang buong clone ng Mercurial na repositoryo na gusto mong palitan:

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

Ang susunod na hakbang ay ang paggawa ng isang may-akda na pagmamapa ng file. Ang Mercurial ay medyo mas mapagpatawad kaysa sa Git para sa kung ano ang ilalagay nito sa may-akda na field para sa mga hanay ng pagbabago, kaya ito ay isang magandang panahon upang maglinis ng bahay. Ang paglikha nito ay iisang linya ng utos sa isang bash na shell:

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

Ito ay lilipas ng ilang mga segundo, depende sa kung gaano kahaba ang kasaysayan ng iyong proyekto, at pagkatapos ang /tmp/authors na file ay magmumukhang katulad nito:

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

Sa halimbawang ito, ang parehong tao (Bob) ay gumawa ng mga hanay ng pagbabago sa ilalim ng apat na iba’t ibang mga pangalan, isa nito ay talagang nagmumukhang tama, at isa nito ay magiging ganap na imbalido para sa isang Git na commit. Ang Hg-fast-export ay hahayaan tayong ayusin ito sa pamamagitan ng pagdagdag ng ={new name and email address} sa dulo ng bawat linya na gusto nating baguhin, at magtatanggal ng mga linya para sa anumang mga username na gusto nating hindi pakialaman. Kung ang lahat ng mga username ay magmumukhang maayos, hindi na natin kailangan ang file na ito. Sa halimbawang ito, gusto natin na ang ating mga file ay magmumukhang katulad nito:

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

Ang susunod na hakbang ay ang paggawa ng ating bagong Git na repositoryo, at patakbuhin ang export na iskrip:

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

Ang -r na flag ay sasabihan ang hg-fast-export kung saan hahanapin ang Mercurial na repositoryo na gusto nating palitan, at ang -A na flag ay sasabihan ito kung saan hahanapin ang may-akda na pag-map na file. Ang iskrip ay magpa-parse ng Mercurial na hanay ng mga pagbabago at papalitan ang mga ito sa isang iskrip para sa "fast-import" na tampok ng Git (na ating tatalakayin nang detalyado sa kaunting saglit mamaya). Ito ay lilipas ng saglit (bagaman ito ay mas mabilis kaysa sa paglipas nito sa network), at ang output ay patas na masalita:

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

Mukhang iyan lang ang lahat ng nilalaman nito. Lahat ng mga tag ng Mercurial ay napalitan na ng mga tag ng Git, at ang mga branch ng Mercurial at mga bookmark ay napalitan na ng mga branch ng Git. Ngayon ay handa ka nang mag-push ng repositoryo sa bago nitong server-side na tahanan:

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

Bazaar

Ang Bazaar ay isang DVCS na kasangkapan na masyadong katulad ng Git, at bilang isang resulta ito ay sobrang matapat upang magpalit ng isang Bazaar na repositoryo patungo sa isang Git na repositoryo. Upang mapatupad ito, kakailanganin mong mag-import ng bzr-fastimport na plugin.

Pagkukuha ng bzr-fastimport na plugin

Ang pamamaraan para sa pag-install ng fastimport na plugin ay naiiba sa katulad ng UNIX na operating na sistema at sa Windows. Sa unang kaso, ang pinakasimple ay ang pag-install ng bzr-fastimport na pakete na mag-i-install sa lahat ng kinakailangang mga dependensya.

Halimbawa, gamit ang Debian at derived, gagawin mo ang sumusunod:

$ sudo apt-get install bzr-fastimport

Gamit ang RHEL, gagawin mo ang sumusunod:

$ sudo yum install bzr-fastimport

Gamit ang Fedora, mula sa release 22, ang bagong tagapamahala ng pakete ay dnf:

$ sudo dnf install bzr-fastimport

Kung ang pakete ay hindi maaaring gamitin, maaari mong i-install ito bilang isang plugin:

$ mkdir --parents ~/.bazaar/plugins/bzr     # gumagawa ng nararapat na mga folder para sa mga plugin
$ cd ~/.bazaar/plugins/bzr
$ bzr branch lp:bzr-fastimport fastimport   # mag-i-import ng fastimport na plugin
$ cd fastimport
$ sudo python setup.py install --record=files.txt   # mag-i-install ng plugin

Upang paganahin ang plugin na ito, kakailanganin mo ang fastimport na modyul ng Python. Maaari mong suriin kung ito ay nandoon o wala at i-install ito gamit ang sumusunod na mga utos:

$ python -c "import fastimport"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ImportError: No module named fastimport
$ pip install fastimport

Kung ito ay hindi maaaring gamitin, maaari mong i-download ito sa address na https://pypi.python.org/pypi/fastimport/.

Sa pangalawang kaso (sa Windows), ang bzr-fastimport ay awtomatikong nag-install gamit ang standalone na bersyon at ang default na pag-install (hayaan ang lahat ng mga checkbox na naka-check). Kaya wala kang gagawin sa kasong ito.

Sa puntong ito, ang paraan upang mag-import ng isang Bazaar na repositoryo ay naiiba ayon sa kung mayroon kang isang solong branch o nagtatrabaho ka sa isang repositoryo na may ilang mga branch.

Proyekto na may isang solong branch

Ngayon mag-cd sa iyong direktoryo na naglalaman ng iyong Bazaar na repositoryo at simulan ang Git na repositoryo:

$ cd /path/to/the/bzr/repository
$ git init

Ngayon, maaari mong simpleng i-export ang iyong Bazaar na repositoryo at palitan ito ng isang Git na repositoryo gamit ang sumusunod na mga utos:

$ bzr fast-export --plain . | git fast-import

Nakadepende sa laki ng proyekto, ang iyong Git na repositoryo ay nabuo lilipas mula sa ilang mga segundo hanggang sa ilang mga minuto.

Kaso ng isang proyekto na may isang pangunahing branch at isang tinatrabahong branch

Maaari ka ring mag-import ng isang Bazaar na repositoryo na naglalaman ng mga branch. Ipalagay natin na ikaw ay may dalawang mga branch: ang isay nagrerepresenta ng pangunahing branch (myProject.trunk), ang iba pa ay ang tinatrabahong branch (myProject.work).

$ ls
myProject.trunk myProject.work

Gumawa ng Git na repositoryo at mag-cd sa ito:

$ git init git-repo
$ cd git-repo

I-pull ang master na branch sa git:

$ bzr fast-export --export-marks=../marks.bzr ../myProject.trunk | \
git fast-import --export-marks=../marks.git

I-pull ang tinatrabahong branch sa Git:

$ bzr fast-export --marks=../marks.bzr --git-branch=work ../myProject.work | \
git fast-import --import-marks=../marks.git --export-marks=../marks.git

Ngayon ang git branch ay magpapakita sa iyo ang master na branch pati na rin ang work branch. Suriin ang mga log upang siguraduhing ang mga ito ay kompleto at alisin ang marks.bzr at marks.git na mga file.

Pagsasabay-sabay ng staging na lawak

Kung anuman ang bilang ng mga branch na mayroon ka at ang import na pamamaraan na ginamit mo, ang iyong staging na lawak ay hindi nagkakasabay sa HEAD, at gamit ang import para sa iilang mga branch, ang iyong tinatrabahong direktoryo ay hindi rin nagkakasabay-sabay. Ang sitwasyong ito ay madaling malutas sa pamamagitan ng sumusunod na utos:

$ git reset --hard HEAD

Pagwawalang bahala sa mga file na hindi pinansin gamit ang .bzrignore

Ngayon tingnan natin ang mga file na hindi papansinin. Ang unang bagay na gagawin ay ang pagpangalan muli sa .bzrignore ng .gitignore. Kung ang .bzrignore na file ay naglalaman ng isa o higit pang mga linya na nagsisimula sa "!!" o "RE:", kailangan mong baguhin ito at marahil gumawa ng iilang .gitignore na mga file upang eksaktong hindi pansinin ang parehong mga file na hindi pinapansin ng Bazaar.

Sa katupasan, kailangan mong gumawa ng isang commit na naglalaman ng pagbabagong ito para sa paglipat:

$ git mv .bzrignore .gitignore
$ # modify .gitignore if needed
$ git commit -am 'Migration from Bazaar to Git'

Pagpapadala ng iyong repositoryo sa server

Narito tayo! Ngayon maaari mong i-push ang repositoryo sa bago nitong tahanan na server:

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

Ang iyong Git na repositoryo ay handa nang gamitin.

Perforce

Ang susunod na sistema na iyong titingnan ay ang pag-import mula sa Perforce. Tulad ng ating natalakay sa itaas, mayroong dalawang mga paraan upang hayaan ang Git at Perforce na makapag-usap sa isa’t isa: ang git-p4 at Perforce na Git Fusion.

Perforce na Git Fusion

Ang Git Fusion ay ginagawang patas na hindi masakit ang prosesong ito. I-kompigura lamang ang mga setting ng iyong proyekto, mga user na mapping, at mga branch gamit ang isang kompigurasyon na file (bilang natalakay sa Pagsasanib ng Git), at i-clone ang repositoryo. Ang Git Fusion ay iiwanan ka ng isang nagmumukhang katulad ng isang katutubong repositoryo ng Git, na handa nang i-push sa isang katutubong Git na host kung gugustuhin mo. Maaari mo ring gamitin ang Perforce bilang iyong Git na host kung gusto mo.

Git-p4

Ang Git-p4 din ay maaaring magsilbi bilang isang import na kasangkapan. Bilang isang halimbawa, ii-import natin ang Jam na proyekto mula sa Perforce na Publikong Depot. Upang itakda ang iyong kliyente, dapat mong i-export ang P4PORT na environment na variable upang tumuro sa Perforce na depot:

$ export P4PORT=public.perforce.com:1666

Upang makasunod, kakailanganin mo ng isang Perforce na depot na kukonektahin. Gagamitin natin ang publikong depot sa public.perforce.com para sa ating mga halimbawa, ngunit maaari mong gamitin ang anumang depot na may access ka.

Patakbuhin ang git p4 clone na utos upang ii-import ang Jam na proyekto mula sa Perforce na server, tinutustusan ang depot at proyekto na landas at ang landas kung saan gusto mong ii-import ang proyekto:

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

Ang partikular na proyektong ito ay mayroon lamang isang branch, ngunit kung ikaw ay mayroong mga branch na nakompigura gamit ang mga view ng branch (o isang hanay ng mga direktoryo lamang), maaari mong gamitin ang --detect-branches na flag sa git p4 clone upang ii-import din ang lahat na mga branch ng proyekto. Tingnan ang Pag-branch para sa kaunting karagdagang detalye para rito.

Sa puntong ito ikaw ay malapit nang matapos. Kung pupunta ka sa p4import na direktoryo at papatakbuhin ang git log, maaari mong tingnan ang iyong na-import na trabaho:

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

Makikita mo na ang git-p4 ay nag-iwan ng isang identifier sa bawat commit na mensahe. Maayos lamang na panatilihin ang identifier doon, sa kasong kailangan mong tukuyin ang Perforce na numero ng pagbabago mamaya. Subalit, kung gusto mong tanggalin ang identifier, ngayon ang panahon na gagawin mo ito – bago ka magsimulang gumawa ng trabaho sa bagong repositoryo. Maaari mong gamitin ang git filter-branch upang tanggalin ang mga string ng identifier nang sama-sama.

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

Kung patatakbuhin mo ang git log, makikita mo na ang lahat ng SHA-1 na mga checksum para sa mga commit ay nabago, ngunit ang git-p4 na mga string ay wala na sa commit na mga mensahe:

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

Ang iyong import ay handa nang i-push pataas sa iyong bagong Git na server.

TFS

Kung ang iyong koponan ay nagpapalit ng kanilang source control mula sa TFVC patungo sa Git, gugustuhin mo makuha ang pagpapalit na nasa pinakamataas na katapatan. Nangangahulugan ito na, habang tayo ay sumakop sa kapwa git-tfs at git-tf para sa interop na seksyon, sasakupin lang natin ang git-tfs para sa bahaging ito, dahil ang git-tfs ay sumusuporta sa mga branch, at ito ay labis na mahirap gamit ang git-tf.

Ito ay isang iisang-daanan ng pagpapalit. Ang resultang Git na repositoryo ay hindi maaaring kumunekta sa orihinal na TFVC na proyekto.

Ang unang bagay na gagawin ay ang pag-map ng mga username. Ang TFVC ay patas na liberal na mayroong kung anumang pumupunta sa may-akda na field para sa mga hanay ng pagbabago, ngunit gusto ng Git ng isang nababasa-ng-tao na pangalan at email address. Maaari mong makuha ang impormasyong ito mula sa tf na command-line na kliyente, kagaya nito:

PS> tf history $/myproject -recursive > AUTHORS_TMP

Kinukuha nito lahat ng mga hanay ng pagbabago sa kasaysayan ng proyekto at ilalagay ito sa AUTHORS_TMP na file na ipo-proseso natin upang katasin ang datos ng User na kolum (yung pangalawa). Buksan ang file at hanapin kung anong mga karakter nagsisimula at nagwawakas ang kolum at palitan, sa sumusunod na command-line, ang mga parameter na 11-20 sa cut na utos kasama ang mga nakita:

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

Ang cut na utos ay nagpapanatili lamang ng mga karakter sa pagitan ng 11 at 20 mula sa bawat linya. Ang tail na utos ay lumalaktaw sa unang dalang mga linya, na mga field header at ASCII-art na mga underline. Ang resulta ng lahat ng ito ay naka-pipe sa sort at uniq upang matanggal ang mga umuulit, at na-save sa isang file na nakapangalang AUTHORS. Ang susunod na hakbang ay manwal; upang ang git-tfs ay makagawa ng epektibang paggamit ng file na ito, bawat linya ay dapat nasa ganitong format:

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

Ang bahagi na nasa kaliwa ay ang “User” na field mula sa TFVC, at ang bahagi na nasa kanang banda ng equals na tanda ay ang user name na magagamit para sa Git na mga commit.

Kapag mayroon ka nitong file, ang susunod na bagay na gagawin ay gumawa ng isang buong clone ng TFVC na proyekto kung saan ka interesado:

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

Susunod ay gugustuhin mong linisin ang git-tfs-id na mga seksyon mula sa ibaba ng commit na mga mensahe. Ang sumusunod na utos ay gagawa sa iyon:

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

Iyon ay gumagamit ng sed na utos mula sa Git-bash na environment upang palitan ang anumang linya na nagsisimula sa “git-tfs-id:” na may kawalan ng laman, kung saan hindi papansinin ng Git.

Kapag iyon ay natapos, handa ka nang magdagdag ng isang panibagong remote, mag-push pataas ng lahat ng iyong mga branch, at hikayatin ang iyong koponan na magsimulang magtrabaho mula sa Git.

Isang Pasadyang Importer

Kung ang iyong sistema ay hindi isa sa nasa itaas, kailangan mong maghanap ng isang importer sa online - ang kalidad na mga importer ay maaaring magamit para sa maraming ibang mga sistema, kalakip na ang CVS, Clear Case, Visual Source Safe, kahit isang direktoryo ng mga archive. Kung wala sa mga kasangkapan ang gumagana para sa iyo, mayroon kang isang mas nakakubling kasangkapan, o kung hindi man ay kailangan mo ng isang mas pasadyang pag-import na proseso, kailangan mong gamitin ang git fast-import. Ang utos na ito ay nagbabasa ng simpleng mga tagubilin mula sa stdin upang magsulat ng nakatukoy na Git na datos. Mas madaling gumawa ng mga object ng Git sa ganitong paraan kaysa sa pagpapatakbo ng hilaw na mga utos ng Git o subukang sumulat ng hilaw na mga object (tingnan ang Mga Panloob ng GIT para sa karagdang impormasyon). Sa paraang ito, maaari kang magsulat ng isang import na iskrip na nagbabasa ng nararapat na impormasyon sa labas ng sistema na iyong ini-import at tuluyang ipi-print ang mga tagubilin sa stdout. Maaari mo na ring patakbuhin ang programang ito at i-pipe ang mga output nito sa pamamagitan ng git fast-import.

Upang madaliang ipakita, magsusulat ka ng isang simpleng importer. Ipagpalagay na ikaw ay nagtatrabaho sa current, iba-back up mo ang iyong proyekto sa pamamagitan ng paminsan-minsang pagkopya ng direktoyo sa isang naka-time-stamp na back_YYYY_MM_DD na direktoryo ng backup, at gusto mong i-import ito sa Git. Ang istraktura ng iyong direktoryo ay magmumukhang katulad nito:

$ ls /opt/import_from
back_2014_01_02
back_2014_01_04
back_2014_01_14
back_2014_02_03
current

Upang mag-import ng isang Git na direktoryo, kailangan mong pagsuriin kung paano nag-iimbak ang Git ng datos nito. Tulad ng maaari mong matandaan, ang Git ay panimulang isang naka-link na listahan ng commit na mga object na tumuturo sa isang snapshot ng nilalaman. Ang kailangan mo lang gawin ay sabihan ng fast-import kung ano ang content na mga snapshot, ano ang commit na datos na tumuturo sa kanila, at ang pagkakaayos ng pagpasok nila. Ang iyong estratehiya ay ang pagpunta sa mga snapshot nang iisa sa bawat pagkakataon at gumawa ng mga commit gamit ang mga nilalaman sa bawat direktoryo, pag-ugnayin ang bawat commit pabalik sa isang nakaraan.

Katulad ng ginawa natin sa An Example Git-Enforced Policy, susulatin natin ito sa Ruby, dahil ito ay kadalasang ating trabaho at malamang ito ay madaling basahin. Maaari mong isulat ang halimbawang ito ng sobrang madali sa anumang pamilyar sa iyo – kailangan nitong i-print ang nararapat na impormasyon sa stdout At, kung nagpapatakbo ka sa Windows, nangangahulugan itong kailangan mo ng isang espesyal na pag-iingat upang hindi magpakilala ng carriage na mga return sa hulihan ng iyong mga linya – ang git fast-import ay sobrang partikular tungkol sa kagustuhan ng mga line feed (LF) hindi ang carriage return line feeds (CRLF) na ginagamit ng Windows.

Upang magsimula, magbabago ka sa target na direktoryo at tutukuyin ang bawat subdirectory, kung saan ang bawat isa ay isang snapshot na gusto mong i-import bilang isang commit. Magbabago ka sa bawat subdirectory at ipi-print ang mga utos na nararapat upang i-export ito. Ang iyong paunang pangunahing loop ay magmumukhang katulad nito:

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

Patatakbuhin mo ang print_export sa loob ng bawat direktoryo, na kukunin ang manipesto at marka ng nakaraang snapshot at ibabalik ang manipesto at marka nito; sa paraang iyon, maaari mong i-ugnay nang maayos. Ang “Mark” ay ang fast-import na termino para sa isang identifier na ibinibigay mo sa isang commit; habang ikaw ay gumagawa ng mga commit, binibigyan mo ang bawat isa ng isang marka na maaari mong gamitin upang i-ugnay sa ito mula sa ibang mga commit. Kaya, ang unang bagay na gagawin sa iyong print_export na paraan ay ang paglikha ng isang marka mula sa pangalan ng direktoryo:

mark = convert_dir_to_mark(dir)

Gagawin mo ito sa pamamagitan ng paggawa ng isang array ng mga direktoryo at paggamit ng index na halaga bilang marka, dahil ang isang marka ay dapat isang integer. Ang iyong paraan ay magmumukhang katulad nito:

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

Ngayon na mayroon ka nang isang integer na representasyon ng iyong commit, kailangan mo ng isang petsa para sa commit na metadata. Dahil ang petsa ay ipinahayag sa pangalan ng direktoryo, kailangan mong i-parse ito. Ang susunod na linya sa iyong print_export na file ay:

date = convert_dir_to_date(dir)

kung saan ang convert_dir_to_date ay nakatakda bilang:

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

Iyon ay nagbabalik ng isang integer na halaga para sa petsa ng bawat direktoryo. Ang huling piraso ng meta-information na kailangan mo sa bawat commit ay ang committer na datos, na iyong na-hardcode sa global na variable:

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

Ngayon ikaw ay handa nang simulan ang pag-printa ng commit na datos para sa iyong importer. Ang paunang impormasyon ay nagtatalakay na nagtatakda ka ng isang commit na object at kung anong branch ito naroroon, sinusundan ng marka na iyong nalikha, ang committer na impormasyon at commit na mensahe, at ang nakaraang commit, kung anuman. Ang code ay magmumukhang katulad nito:

# 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

Na-hardcode mo ang time zone (-0700) dahil ang paggawa nito ay sobrang madali. Kung ikaw ay nag-iimport mula sa ibang sistema, kailangan mong tukuyin ang time zone bilang isang offset. Ang commit na mensahe ay dapat mapahayag sa isang espeyal na format:

data (size)\n(contents)

Ang format ay binubuo ng salita na datos, ang laki ng datos na babasahin, isang newline, at sa wakas ang data. Dahil kailangan mong gumamit ng parehong format upang tukuyin ang mga nilalaman ng file mamaya, gagawa ka ng isang katulong na paraan, export_data:

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

Ang natitira lamang na tutukuyin ay ang mga nilalaman ng file para sa bawat snapshot. Ito ay madali, dahil mayroon ka sa bawat isa sa isang direktoryo – maaari mong i-print ang deleteall na utos na sinusundan ng mga nilalaman sa bawat file sa direktoryo. Ang Git ngayon ay naaangkop na iri-record ang bawat snapshot:

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

Tandaan: Dahil maraming mga sistema ay nag-iisip sa kanilang mga revision bilang mga pagbabago mula sa isang commit tungo sa iba, ang fast-import ay maaari ring kumuha ng mga utos sa bawat commit upang tukuyin kung anong mga file ang naidagdag, natanggal, o nabago at ano ang mga bagong nilalaman. Maaari mong kalkulahin ang mga pagkakaiba sa pagitan ng mga snapshot at magbigay lamang nitong datos, ngunit ang paggawa nito ay mas kumplikado – marahil ikaw din ay magbibigay ng Git ng lahat ng datos at hahayaan itong alamin ito. Kung ito ay mas mainam para sa iyong data, suriin ang fast-import na man na pahina para sa mga detalye tungkol kung paano magbigay ng iyong datos sa ganitong paraan.

Ang format para sa pag-lista ng bagong mga nilalaman ng file o pagtutukoy ng isang nabagong file gamit ang bagong mga nilalaman ay tulad ng sumusunod:

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

Dito, ang 644 na mode (kung mayroon kang maipapatupad na mga file, kailangan mong tuklasin at sa halip ay tukuyin ang 755), at sinasabi ng inline na iyong i-lilista ang mga nilalaman nang madalian pagkatapos ng linyang ito. Ang iyong inline_data na paraan ay magmumukhang katulad nito:

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

Gamitin mo ulit ang export_data na paraan na iyong naitakda kamakailan lang, dahil ito ay pareho sa paraang itinukoy mo ang iyong datos ng commit na mensahe.

Ang huling bagay na kailangan mong gawin ay pagpapabalik ng kasalukuyang marka upang ito ay maaaring mapasa sa susunod na pag-uulit:

return mark

Kung ikaw ay nagpapatakbo sa Windows kailangan mong siguraduhing magdagdag ka ng isang karagdagang hakbang. Tulad ng nabanggit noon, ang Windows ay gumagamit ng CRLF para sa new line na mga karakter habang inaasahan ng git fast-import ang LF lamang. Upang malaktawan ang problemang ito at masayang gumawa ng git fast-import, kailangan mong sabihan ang ruby na gumamit ng LF sa halip na CRLF:

$stdout.binmode

Iyon lang iyon. Narito ang iskrip sa pangkabuuan nito:

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

# Maglo-loop sa mga direktoryo
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

Kung papatakbuhin mo ang iskrip na ito, makakatanggap ka ng nilalaman na magmumukhang katulad nito:

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

Upang mapatakbo ang importer, i-pipe ang output na ito gamit ang git fast-import habang nasa Git na direktoryo na kung saan gusto mong mag-import. Maaari kang bumuo ng bagong direktoryo at magpatakbo ng git init sa ito para sa isang panimulang punto, at patakbuhin ang iyong iskrip:

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

Batay sa iyong nakita, kapag ito ay matagumpay na matatapos, bibigyan ka nito ng bungkos ng mga istatistika tungkol sa kung ano ang natapos nito. Sa kasong ito, nag-import ka ng kabuuang 13 na mga object para sa 4 na mga commit sa 1 branch. Ngayon, maaari mong patakbuhin ang git log upang makita ang iyong bagong kasaysayan:

$ 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

Ayan – isang maganda, malinis na Git na repositoryo. Importanteng tandaan na walang na-check out – wala kang anumang file sa iyong tinatrabahong direktoryo sa simula pa lang. Upang makuha ang mga ito, dapat mong i-reset ang iyong branch sa kung saan ang master ngayon:

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

Maaari kang gumawa ng mas higit pa gamit ang fast-import na kasangkapan – humawak ng iba’t ibang mga mode, binary na datos, maramihang mga branch at pag-merge, mga tag, mga tagapagpabatid ng pag-unlad, at marami pa. Marami pang mga halimbawa ng mas kumplikadong mga pangyayari ang maaaring magamit ng contrib/fast-import na direktoryo ng Git na source code.