Kötü bir birleştirmeyi nasıl düzeltirsiniz ve iyi taahhütlerinizi sabit bir birleştirmeye nasıl tekrar koyarsınız?


407

filename.origŞimdiye kadar fark etmeden, yanlışlıkla birkaç komisyon önce depoma istenmeyen bir dosyayı ( birleştirme çözümlerken) taahhüt ettim. Dosyayı depo geçmişinden tamamen silmek istiyorum.

Değişiklik geçmişini filename.origen başta depoya asla eklenmeyecek şekilde yeniden yazmak mümkün müdür ?



Yanıtlar:


297

Durumunuz soruda açıklanan durum değilse lütfen bu tarifi kullanmayın. Bu tarif, kötü bir birleştirme düzeltmek ve iyi taahhütlerinizi sabit bir birleştirme halinde tekrarlamak içindir.

Her ne filter-branchistediğini yapacak, oldukça karmaşık bir komuttur ve muhtemelen bunu yapmak seçsin git rebase. Muhtemelen kişisel bir tercihtir. filter-branchbunu tek, biraz daha karmaşık bir komutla yapabilirken, rebaseçözüm eşdeğer mantıksal işlemleri birer birer gerçekleştiriyor.

Aşağıdaki tarifi deneyin:

# create and check out a temporary branch at the location of the bad merge
git checkout -b tmpfix <sha1-of-merge>

# remove the incorrectly added file
git rm somefile.orig

# commit the amended merge
git commit --amend

# go back to the master branch
git checkout master

# replant the master branch onto the corrected merge
git rebase tmpfix

# delete the temporary branch
git branch -d tmpfix

(Aslında geçici bir şubeye ihtiyacınız olmadığını unutmayın, bunu 'müstakil KAFA' ile yapabilirsiniz, ancak geçici şubeyi kullanmak yerine komuta git commit --amendtedarik etme adımı tarafından oluşturulan kesin kimliği not etmeniz gerekir. git rebaseadlandırın.)


6
Daha git rebase -ihızlı ve hala kolay olmaz mıydı? $ git rebase -i <sh1-of-merge> Doğru olanı "düzenle" olarak işaretleyin Bunu ben yaptım. Muhtemelen bir şey eksik.
Wernight

12
git rebase -iözellikle birden fazla rebase-y işleminiz olduğunda çok kullanışlıdır, ancak birisinin omzunun üzerinden gerçekten bakmadığınızda ve editörleriyle ne yaptıklarını görebildiğinizde doğru bir şekilde tanımlamak doğru bir acıdır. Ben vim kullanıyorum, ancak herkes memnun olmaz: "ggjcesquash <Esc> jddjp: wq" ve "Geçerli ikinci satırdan sonra üst satırı taşıyın ve dördüncü satırdaki ilk kelimeyi şimdi 'düzenle' olarak kaydedin ve kaydedin quit "hızlı bir şekilde gerçek adımlardan daha karmaşık görünüyor. Normalde bazı eylemler --amendve --continueeylemlerle de sonuçlanırsınız .
CB Bailey

3
Bunu yaptım ama aynı mesajla değiştirilmiş olanın üzerine yeni bir taahhüt tekrar uygulandı. Görünüşe göre git, istenmeyen dosyayı içeren eski, değiştirilmemiş komut ile diğer şubeden sabit taahhüdü arasında 3 yönlü birleştirme yaptı ve böylece dosyayı yeniden uygulamak için eskisinin üstünde yeni bir taahhüt oluşturdu.

6
@UncleCJ: Dosyanız birleştirme işlemine eklendi mi? Bu önemli. Bu tarif, kötü birleştirme taahhüdü ile başa çıkmak için tasarlanmıştır. İstenmeyen dosyanız tarihte normal bir işlemde eklenmişse işe yaramaz.
CB Bailey

1
Tüm bunları smartgit kullanarak ve nasıl bir terminal kullanarak yapabileceğime şaşırdım! Tarif için teşekkürler!
cregox

209

Giriş: 5 Çözümünüz Var

Orijinal afişin belirtileri:

Yanlışlıkla istenmeyen bir dosyayı işledim ... birkaç kez önce depoma ... Dosyayı depo geçmişinden tamamen silmek istiyorum.

Değişiklik geçmişini filename.origen başta depoya asla eklenmeyecek şekilde yeniden yazmak mümkün müdür ?

Bir dosyanın geçmişini git'ten tamamen kaldırmanın birçok farklı yolu vardır:

  1. Değişiklik taahhütleri.
  2. Sert sıfırlamalar (muhtemelen artı bir rebase).
  3. Etkileşimli olmayan yeniden taban.
  4. Etkileşimli referanslar.
  5. Filtreleme dalları.

Orijinal poster söz konusu olduğunda, taahhütte değişiklik yapmak kendi başına bir seçenek değildir, daha sonra birkaç ek taahhütte bulunduğundan, ancak eksiksizlik uğruna, bunun nasıl yapılacağını açıklayacağım, önceki taahhütlerini değiştirmek için.

Tüm bu çözümlerin geçmişi / taahhütleri bir şekilde değiştirmeyi / yeniden yazmayı içerdiğini unutmayın, bu nedenle taahhütlerin eski kopyalarına sahip olan herkes, tarihlerini yeni tarihle yeniden senkronize etmek için ekstra iş yapmak zorunda kalacaktır.


Çözüm 1: Değişiklik Taahhütleri

Önceki taahhüdünüzde yanlışlıkla bir değişiklik yaptıysanız (dosya eklemek gibi) ve bu değişikliğin geçmişinin artık var olmasını istemiyorsanız, dosyayı kaldırmak için önceki taahhüdünde değişiklik yapabilirsiniz:

git rm <file>
git commit --amend --no-edit

Çözüm 2: Donanımdan Sıfırlama (Muhtemelen Artı Bir Rebase)

Çözüm # 1 gibi, sadece önceki taahhüdünüzden kurtulmak istiyorsanız, aynı zamanda ebeveynine sert bir sıfırlama yapma seçeneğiniz de vardır:

git reset --hard HEAD^

Bu komut önceki 1 için şube sabit sıfırlar st ebeveyn taahhüt.

Bununla birlikte , orijinal poster gibi, değişikliği geri almak istediğiniz taahhütten sonra birkaç taahhütte bulunduysanız, yine de değiştirmek için sert sıfırlamalar kullanabilirsiniz, ancak bunu yapmak bir rebase kullanmayı da içerir. Tarihte bir taahhüdü değiştirmek için kullanabileceğiniz adımlar şunlardır:

# Create a new branch at the commit you want to amend
git checkout -b temp <commit>

# Amend the commit
git rm <file>
git commit --amend --no-edit

# Rebase your previous branch onto this new commit, starting from the old-commit
git rebase --preserve-merges --onto temp <old-commit> master

# Verify your changes
git diff master@{1}

Çözüm 3: Etkileşimli Olmayan Rebase

Bu, yalnızca bir taahhüdü tarihten tamamen kaldırmak istiyorsanız işe yarayacaktır:

# Create a new branch at the parent-commit of the commit that you want to remove
git branch temp <parent-commit>

# Rebase onto the parent-commit, starting from the commit-to-remove
git rebase --preserve-merges --onto temp <commit-to-remove> master

# Or use `-p` insteda of the longer `--preserve-merges`
git rebase -p --onto temp <commit-to-remove> master

# Verify your changes
git diff master@{1}

Çözüm 4: Etkileşimli Rebases

Bu çözüm, # 2 ve # 3 çözümleriyle aynı şeyleri gerçekleştirmenize izin verecektir, yani değişiklikleri önceki önceki taahhüdünüzden daha fazla değiştirebilir veya kaldırabilirsiniz, böylece kullanmayı seçtiğiniz çözüm size bağlıdır. Etkileşimli yeniden basmalar performans nedeniyle yüzlerce taahhüdü yeniden basmaya uygun değildir, bu nedenle bu tür durumlarda etkileşimli olmayan bas basını veya filtre dalı çözümünü (aşağıya bakın) kullanırdım.

Etkileşimli yeniden başlamaya başlamak için aşağıdakileri kullanın:

git rebase --interactive <commit-to-amend-or-remove>~

# Or `-i` instead of the longer `--interactive`
git rebase -i <commit-to-amend-or-remove>~

Bu, git işleminin geçmişini değiştirmek veya kaldırmak istediğiniz işlemin üst öğesine geri sarmasına neden olur. Daha sonra, editör git'in ayarlandığı şekilde geri sarma işlemlerinin bir listesini size sunacaktır (bu varsayılan olarak Vim'dir):

pick 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
pick 7668f34 Modify Bash config to use Homebrew recommended PATH
pick 475593a Add global .gitignore file for OS X
pick 1b7f496 Add alias for Dr Java to Bash config (OS X)

Değiştirmek veya kaldırmak istediğiniz taahhüt bu listenin en üstünde yer alacaktır. Kaldırmak için listedeki satırını silmeniz yeterlidir. Aksi takdirde, yerine 1 "düzenle" ile "çekme" st hattı, bu yüzden istiyorum:

edit 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`

Sonra girin git rebase --continue. Taahhütü tamamen kaldırmayı seçtiyseniz, yapmanız gereken tek şey (doğrulama dışında, bu çözüm için son adıma bakın). Öte yandan, taahhüdü değiştirmek isterseniz, git taahhüdü yeniden uygular ve ardından yeniden temeli duraklatır.

Stopped at 00ddaacab0a85d9989217dd9fe9e1b317ed069ac... Add symlinks
You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue

Bu noktada, dosyayı kaldırabilir ve taahhüdü değiştirebilir, ardından yeniden başlamaya devam edebilirsiniz:

git rm <file>
git commit --amend --no-edit
git rebase --continue

Bu kadar. Son adım olarak, taahhüdü değiştirmiş veya tamamen kaldırmış olsanız da, şubenizde yeniden tabandan önceki durumuna dağıtarak başka hiçbir beklenmedik değişiklik yapılmadığını doğrulamak her zaman iyi bir fikirdir:

git diff master@{1}

Çözüm 5: Dalları Filtreleme

Son olarak, bir dosyanın varlığının tüm izlerini geçmişten tamamen silmek istiyorsanız, bu çözüm en iyisidir ve diğer çözümlerin hiçbiri göreve tamamen uygun değildir.

git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>'

Bu <file>, kök taahhüdünden başlayarak tüm taahhütlerden kaldırılacaktır . Bunun yerine, yalnızca taahhüt aralığını yeniden yazmak istiyorsanız HEAD~5..HEAD, bunu bu cevaptafilter-branch belirtildiği gibi ek bir argüman olarak iletebilirsiniz :

git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD

Yine, filter-branchtamamlandıktan sonra , filtreleme işleminden önce şubenizi önceki durumuyla ayırarak beklenmedik başka değişikliklerin olmadığını doğrulamak genellikle iyi bir fikirdir:

git diff master@{1}

Filtre Dalı Alternatifi: BFG Repo Temizleyici

BFG Repo Temizleyici aracının daha hızlı çalıştığını duydum git filter-branch, bu yüzden bir seçenek olarak da kontrol etmek isteyebilirsiniz. Filtre dalı belgelerinde geçerli bir alternatif olarak resmen belirtilmiştir :

git-filter-branch, Git geçmişinizin kabuk komut dosyası yeniden yazma işlemlerini yapmanızı sağlar, ancak büyük dosyalar veya parolalar gibi istenmeyen verileri kaldırıyorsanız muhtemelen bu esnekliğe ihtiyacınız yoktur . Bu işlemler için git-filter-branşına JVM tabanlı bir alternatif olan BFG Repo- Cleaner'ı, bu kullanım durumları için tipik olarak en az 10-50x daha hızlı ve oldukça farklı özelliklere sahip olmak isteyebilirsiniz:

  • Bir dosyanın belirli bir sürümü tam olarak bir kez temizlenir . BFG, git-filter-branch'dan farklı olarak, bir dosyayı geçmişinizde nereye veya ne zaman işlendiğine bağlı olarak farklı şekilde işleme fırsatı vermez. Bu kısıtlama, BFG'nin temel performans avantajını sağlar ve kötü verileri temizleme görevi için çok uygundur - kötü verilerin nerede olduğu umurumda değil , sadece gitmesini istiyorsunuz .

  • Varsayılan olarak BFG, çok katmanlı makinelerin tüm avantajlarından yararlanır ve işleme dosya ağaçlarını paralel olarak temizler. Git-filtre-dal temizler kaydedilmesini sırayla (yani tek dişli bir tarzda), olsa olan her karşı yürütülen komut işlemek mümkün kendi paralellik içermektedir filtreleri yazmak için.

  • Komut seçenekleri sadece örn veriler- istenmeyen kaldırma görevlerine çok daha git-filtre dalından farklı kısıtlayıcı ve adanmış gibidir: --strip-blobs-bigger-than 1M.

Ek kaynaklar

  1. Pro Git § 6.4 Git Araçları - Yeniden Yazma Geçmişi .
  2. git-filter-branch (1) Kılavuz Sayfası .
  3. git-commit (1) Kılavuz Sayfası .
  4. git-reset (1) Manuel Sayfa .
  5. git-rebase (1) Kılavuz Sayfası .
  6. BFG Repo Temizleyici (ayrıca yaratıcının kendisinden gelen bu cevaba bakınız ).

Karmaların filter-branchyeniden hesaplanmasına neden olur mu ? Bir ekip büyük bir dosyanın filtrelenmesi gereken bir repo ile çalışırsa, bunu herkes nasıl repo ile aynı duruma getirecek şekilde yapar?
YakovL

@YakovL. Her şey karmaları yeniden hesaplar. Aslında taahhütler değişmez. Tamamen yeni bir geçmiş oluşturur ve şube işaretçinizi ona taşır. Herkesin aynı geçmişe sahip olmasını sağlamanın tek yolu donanımdan sıfırlamadır.
Mad Physicist

118

O zamandan beri bir şey yapmadıysanız, sadece git rmdosya ve git commit --amend.

Eğer varsa

git filter-branch \
--index-filter 'git rm --cached --ignore-unmatch path/to/file/filename.orig' merge-point..HEAD

Her değişim geçeceği merge-pointiçin HEAD, silme filename.orig ve değişim yeniden. Kullanımı --ignore-unmatchnedense filename.orig bir değişimden eksikse komut başarısız olmayacak demektir. Git-filter-branch kılavuz sayfasındaki Örnekler bölümünden önerilen yol budur .

Windows kullanıcıları için not: Dosya yolu olmalı eğik kullanabilirsiniz


3
Teşekkürler! git filter-branch, cevap olarak verilen rebase örneğinin işe yaramadığı yerde benim için çalıştı: Adımlar işe yaradı, ancak itme başarısız oldu. Bir çekim yaptıktan sonra başarılı bir şekilde itti, ancak dosya hala etraftaydı. Rebase adımlarını yinelemeye çalıştı ve sonra birleşme çatışmalarıyla dağınık hale geldi. Ben burada verilen "Geliştirilmiş Bir Yöntem" olan biraz farklı bir filtre-şube komutu kullandım: github.com/guides/completely-remove-a-file-from-all-revisions git filter-branch -f --index- filtre 'git update-index - dosya adını kaldır' <giriş-revision-sha1>
..HEAD

1
Hangisinin geliştirilmiş yöntem olduğundan emin değilim . Git resmi belgelerine git-filter-branchilki veriyor gibi görünüyor.
Wernight

5
Kontrol zyxware.com/articles/4027/… Ben içeren en eksiksiz ve en basit çözümü bulmakfilter-branch
leontalbot

2
@ atomlar, yerel repoyu uzak olana itmeye çalışacaksanız, git önce uzaktan kumandadan çekilmede ısrar edecektir, çünkü yerel olarak sahip olmadığınız değişiklikler var. Uzaktan kumandayı itmek için --force flag'ı kullanabilirsiniz - dosyaları oradan tamamen kaldıracaktır. Ancak, yalnızca dosyalardan başka bir şeyin üzerine yazmaya zorlamayacağınızdan emin olun.
sol0mka

1
Windows'u kullanırken "değil 'de kullanmayı unutmayın , aksi takdirde yardımcı olmayan bir şekilde "hatalı düzeltme" hatası alırsınız.
cz

49

Bu en iyi yoldur:
http://github.com/guides/completely-remove-a-file-from-all-revisions

Öncelikle dosyaların kopyalarını yedeklediğinizden emin olun.

DÜZENLE

Neon'un düzenlemesi maalesef inceleme sırasında reddedildi.
Aşağıdaki Neons gönderisine bakın, faydalı bilgiler içerebilir!


Örn. *.gzGit deposuna yanlışlıkla kaydedilmiş tüm dosyaları kaldırmak için :

$ du -sh .git ==> e.g. 100M
$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch *.gz' HEAD
$ git push origin master --force
$ rm -rf .git/refs/original/
$ git reflog expire --expire=now --all
$ git gc --prune=now
$ git gc --aggressive --prune=now

Bu hala benim için işe yaramadı mı? (Şu anda git 1.7.6.1 sürümündeyim)

$ du -sh .git ==> e.g. 100M

Neden olduğundan emin değilim, çünkü sadece BİR master şubem vardı. Neyse, nihayet benim git repo gerçekten boş ve çıplak git depoya, örneğin iterek temizlenmiş var

$ git init --bare /path/to/newcleanrepo.git
$ git push /path/to/newcleanrepo.git master
$ du -sh /path/to/newcleanrepo.git ==> e.g. 5M 

(Evet!)

Sonra bunu yeni bir dizine kopyaladım ve onun .git klasörü üzerine taşıdım. Örneğin

$ mv .git ../large_dot_git
$ git clone /path/to/newcleanrepo.git ../tmpdir
$ mv ../tmpdir/.git .
$ du -sh .git ==> e.g. 5M 

(evet! sonunda temizlendi!)

Her şeyin yolunda olduğunu doğruladıktan sonra ../large_dot_gitve ../tmpdirdizinlerini silebilirsiniz (belki birkaç hafta veya ay sonra, her ihtimale karşı ...)


1
"Benim için hala işe yaramadı mı?" yorum
shadi

Harika bir yanıt, ancak --prune-emptyfiltre dalı komutuna eklemenizi öneririz .
03:41

27

Git geçmişini yeniden yazmak, etkilenen tüm taahhüt kimliklerini değiştirmeyi gerektirir ve bu nedenle proje üzerinde çalışan herkesin repoya ait eski kopyalarını silmesi ve geçmişi temizledikten sonra yeni bir klon yapması gerekir. O uygunsuz olur daha fazla insan, daha bunu yapmak için iyi bir nedene ihtiyacım - senin gereksiz dosya gerçekten bir soruna neden değildir, ancak yalnızca sen proje üzerinde çalışıyoruz isterseniz, siz de Git geçmişini temizlemek olabilir için!

Mümkün olduğunca kolay hale getirmek için, Git geçmişinden dosyaları kaldırmak için özel olarak tasarlanmış olan daha basit ve daha hızlı bir alternatif olan BFG Repo-Cleaner'ı kullanmanızı tavsiye ederim git-filter-branch. Burada hayatınızı kolaylaştırmanın bir yolu, varsayılan olarak tüm referansları (tüm etiketler, dallar, vb.) İşleyebilmesidir , ancak aynı zamanda 10 - 50x daha hızlıdır.

Buradaki adımları dikkatle izlemelisiniz: http://rtyley.github.com/bfg-repo-cleaner/#usage - ancak çekirdek bit sadece şu: BFG kavanozunu indirin (Java 6 veya üstü gerektirir) ve bu komutu çalıştırın :

$ java -jar bfg.jar --delete-files filename.orig my-repo.git

Sizin bütün depo tarih taranır ve adlandırılmış herhangi bir dosya filename.orig(yani senin burada değil son taahhüt ) kaldırılacaktır. Bu, git-filter-branchaynı şeyi yapmak için kullanmaktan çok daha kolaydır !

Tam açıklama: BFG Repo-Cleaner'ın yazarıyım.


4
Bu mükemmel bir araçtır: tek bir komut, çok net çıktılar üretir ve her eski taahhüdü yenisiyle eşleşen bir günlük dosyası sağlar . Java yüklemeyi sevmiyorum ama buna değer.
mikemaccana

Bu benim için işe yarayan tek şey ama bunun nedeni git filter-branch'u düzgün çalışmama gibi. :-)
Kevin LaBranche

14
You should probably clone your repository first.

Remove your file from all branches history:
git filter-branch --tree-filter 'rm -f filename.orig' -- --all

Remove your file just from the current branch:
git filter-branch --tree-filter 'rm -f filename.orig' -- --HEAD    

Lastly you should run to remove empty commits:
git filter-branch -f --prune-empty -- --all

1
Tüm cevaplar filtre dalı yolunda gözükse de, bu, geçmişinizdeki TÜM dalları nasıl temizleyeceğinizi vurgulamaktadır.
Cameron Lowell Palmer

4

Sadece Charles Bailey'nin çözümüne eklemek için, daha önceki bir taahhütten istenmeyen dosyaları kaldırmak için bir git rebase -i kullandım ve bir cazibe gibi çalıştı. Adımlar:

# Pick your commit with 'e'
$ git rebase -i

# Perform as many removes as necessary
$ git rm project/code/file.txt

# amend the commit
$ git commit --amend

# continue with rebase
$ git rebase --continue

4

Bulduğum en basit yol leontalbot, Anoopjohn tarafından yayınlanan bir yazı olan (yorum olarak) tarafından önerildi . Bir cevap olarak kendi alanı değerinde olduğunu düşünüyorum:

(Bir bash betiğine dönüştürdüm)

#!/bin/bash
if [[ $1 == "" ]]; then
    echo "Usage: $0 FILE_OR_DIR [remote]";
    echo "FILE_OR_DIR: the file or directory you want to remove from history"
    echo "if 'remote' argument is set, it will also push to remote repository."
    exit;
fi
FOLDERNAME_OR_FILENAME=$1;

#The important part starts here: ------------------------

git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch $FOLDERNAME_OR_FILENAME" -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now

if [[ $2 == "remote" ]]; then
    git push --all --force
fi
echo "Done."

Tüm krediler Annopjohnleontalbot ona işaret ediyor ve işaret ediyor.

NOT

Komut dosyasının doğrulama içermediğini unutmayın, bu nedenle hata yapmadığınızdan ve bir şeyler ters gittiğinde yedek aldığınızdan emin olun. Benim için çalıştı, ama sizin durumunuzda çalışmayabilir. DİKKAT İLE KULLANIN (neler olup bittiğini bilmek istiyorsanız bağlantıyı takip edin).


3

Kesinlikle, git filter-branchgitmenin yolu budur.

Ne yazık ki, filename.origyine de etiketler, reflog girişleri, uzaktan kumandalar ve benzeri tarafından referans verilebileceğinden, repodan tamamen kaldırmaya yeterli olmayacaktır .

Tüm bu referansları da kaldırmanızı ve ardından çöp toplayıcısını çağırmanızı öneririm. Tüm bunları tek bir adımda yapmak için bu web sitesindeki git forget-blobkomut dosyasını kullanabilirsiniz .

git forget-blob filename.orig


1

Temizlemek istediğiniz en son işlem ise git sürüm 2.14.3 (Apple Git-98) ile denedim:

touch empty
git init
git add empty
git commit -m init

# 92K   .git
du -hs .git

dd if=/dev/random of=./random bs=1m count=5
git add random
git commit -m mistake

# 5.1M  .git
du -hs .git

git reset --hard HEAD^
git reflog expire --expire=now --all
git gc --prune=now

# 92K   .git
du -hs .git

git reflog expire --expire=now --all; git gc --prune=nowyapmak çok kötü bir şey. Disk alanınız
bitmedikçe

Bunu işaret ettiğiniz için teşekkürler. Benim repo birçok büyük ikili dosyaları ile gönderildi ve repo tamamen her gece yedeklenir. Yani ben sadece her bit istedim;)
clarkttfu


-1

Ayrıca kullanabilirsiniz:

git reset HEAD file/path


3
Dosya bir taahhüdüne eklenmişse, dosyayı dizinden bile kaldırmaz, dizini dosyanın HEAD sürümüne sıfırlar.
CB Bailey
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.