Git'teki kök işleminden önce bir işlem eklensin mi?


231

Daha önce git deposundaki ilk iki taahhüdün nasıl ezileceğini sordum .

Çözümler oldukça ilginç olsa da, git'teki diğer şeyler kadar zihin çözgüsü olmasa da, projenizin gelişimi boyunca prosedürü birçok kez tekrarlamanız gerekiyorsa, hala meşhur incinme torbasının bir parçası.

Bu yüzden, sadece bir kez acı çekmeyi ve sonra standart etkileşimli rebase'i sonsuza kadar kullanabilmeyi tercih ederim.

Öyleyse yapmak istediğim şey, sadece ilk olmak amacıyla var olan boş bir başlangıç ​​taahhüdüne sahip olmak. Kod yok, hiçbir şey yok. Sadece yer kaplar, böylece yeniden tabanın temeli olabilir.

O zaman sorum şu ki, mevcut bir depoya sahip olmak, ilkinden önce yeni, boş bir taahhüt eklemek ve herkesi ileriye kaydırmak için nasıl gideceğim?


3
;) Sanırım yine de bir cevap gerektiriyor. Tarihin takıntılı bir şekilde düzenlenmesi yoluyla delirmenin birçok yolunu araştırıyorum. Endişelenmeyin, paylaşılan bir depo değil.
kch

11
Bir takıntılı, çılgın tarih editöründen diğerine, soruyu gönderdiğiniz için teşekkürler! ; D
Marco

10
@ Kch'in savunmasında, mükemmel bir meşru neden kendimi bulduğum nedendir: Repoda hiç yakalanmayan tarihi bir versiyonun anlık görüntüsünü eklemek.
Old McStopher

4
Başka bir meşru nedenim var! İlk işleme yeniden başlayabilmek ve bir deponun ilk
işlemine

Yanıtlar:


316

Bunu başarmak için 2 adım vardır:

  1. Yeni bir boş taahhüt oluşturma
  2. Bu boş işlemden başlamak için geçmişi yeniden yazın

newrootKolaylık sağlamak için yeni boş taahhüdü geçici bir şubeye koyacağız .

1. Yeni bir boş taahhüt oluşturun

Bunu yapmanın birkaç yolu vardır.

Sadece sıhhi tesisat kullanma

En temiz yaklaşım Git'in tesisatını sadece doğrudan bir taahhüt oluşturmak için kullanmaktır.

  1. Boş bir dizin için bir ağaç nesnesi oluşturun:

    tree=`git hash-object -wt tree --stdin < /dev/null`
    
  2. Etrafına bir taahhüt sarın:

    commit=`git commit-tree -m 'root commit' $tree`
    
  3. Bir referans oluşturun:

    git branch newroot $commit
    

Kabuğunuzu yeterince iyi biliyorsanız, elbette tüm prosedürü tek bir astar halinde yeniden düzenleyebilirsiniz.

Sıhhi tesisat olmadan

Düzenli porselen komutlarıyla, newrootşubeyi kontrol etmeden ve dizini ve çalışma kopyasını tekrar tekrar güncellemeden boş bir taahhüt oluşturamazsınız, bunun bir nedeni yoktur. Ancak bazıları bunu daha kolay anlayabilir:

git checkout --orphan newroot
git rm -rf .
git clean -fd
git commit --allow-empty -m 'root commit'

Eksikliği Git çok eski sürümlerinde olduğu Not --orphangeçiş checkout, bununla ilk satırı değiştirmek zorunda:

git symbolic-ref HEAD refs/heads/newroot

2. Bu boş işlemden başlamak için geçmişi yeniden yazın

Burada iki seçeneğiniz var: yeniden basma veya temiz bir geçmişi yeniden yazma.

rebasing

git rebase --onto newroot --root master

Bu sadelik erdemine sahiptir. Bununla birlikte, şubedeki her son taahhütteki komisyoncu adını ve tarihini de güncelleyecektir.

Ayrıca, bazı son vaka geçmişlerinde, hiçbir şey içermeyen bir taahhüde dayanıyor olmanıza rağmen, birleşme çatışmaları nedeniyle bile başarısız olabilir.

Geçmişi yeniden yazma

Daha temiz yaklaşım, dalı yeniden yazmaktır. Şunun aksine git rebase, şubenizin hangi taahhütten başladığını araştırmanız gerekir:

git replace <currentroot> --graft newroot
git filter-branch master

Yeniden yazma açıkça ikinci adımda gerçekleşir; açıklanması gereken ilk adım. Yapılması git replacegereken, Git'e, değiştirilmesini istediğiniz bir nesneye her başvuru gördüğünde, bunun yerine nesnenin değiştirilmesine bakması gerektiğini söyler.

İle --graftanahtarı, normalde biraz daha farklı bir şeyler anlatıyor. Henüz bir yedek nesnesi yok diyorsun, ama Değiştirmek istediğiniz <currentroot>kendisinin tam kopyasıyla nesneyi işlemek dışında yani (listelenen bu bir (ler) olmalıdır değiştirme ebeveyn taahhüt (ler) newrootişlemek ). Sonragit replace devam eder ve bu taahhüdü sizin için oluşturur ve ardından bu taahhüdü orijinal taahhüdünüzün yerine geçer.

Şimdi bir git logyaparsanız, şeylerin zaten istediğiniz gibi göründüğünü göreceksiniz: şube başlar newroot.

Bununla birlikte, git replace gerçekte geçmişi değiştirmediğini veya deponuzdan yayılmadığını unutmayın. Yalnızca deponuza bir nesneden diğerine yerel bir yönlendirme ekler. Bunun anlamı, başkasının bu değiştirme işleminin etkisini görmemesi - sadece siz.

Bu yüzden filter-branchadım gerekli. İle git replaceroot düzeltilmiş ebeveyn kaydedilmesini ile tam bir kopyasını oluşturmak işlemek; git filter-branchbu işlemi aşağıdaki tüm taahhütler için de tekrarlar. Tarih, aslında onu yeniden yazabilmeniz için burada yeniden yazılıyor.


1
Bu --onto newrootseçenek gereksizdir; onsuz yapabilirsiniz çünkü geçtiğiniz newrootargüman, yukarı akım argümanı ile aynıdır - newroot.
wilhelmtell

7
Sıhhi tesisat komutları yerine neden porselen kullanmıyorsunuz? Ben değiştirecekti git sembolik-ref BAŞ ref / kafaları / newroot ile git ödeme --orphan newroot
albfan

4
@nenopera: çünkü bu cevap daha önce yazılmıştı git-checkoutbu geçiş vardı. İlk olarak bu yaklaşımdan bahsetmiştim, işaretçi için teşekkürler.
Aristoteles Pagaltzis

1
Yeni kökünüz boş değilse, git rebase --merge -s recursive -X theirs --onto newroot --root mastertüm çakışmaları otomatik olarak çözmek için kullanın ( bu cevaba bakınız ). @AlexanderKuzin
kullanıcı

1
@Geremia Yalnızca son taahhüdü değiştirebilirsiniz, bu nedenle havuzunuz yalnızca kök taahhüdü içeriyorsa, işe yarayabilir, aksi takdirde değiştirilmiş kök taahhüdünün üstündeki repodaki diğer tüm taahhütleri yine de yeniden oluşturmak zorunda kalacaksınız. Ancak o zaman bile, konu kök taahhüdünü değiştirmek istemediğiniz, ancak mevcut kökten önce başka bir tane eklemek istediğinizi ima eder.
kullanıcı

31

Aristoteles Pagaltzis'in ve Uwe Kleine-König'in cevaplarının ve Richard Bronosky'nin yorumunun birleşmesi.

git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d
# touch .gitignore && git add .gitignore # if necessary
git commit --allow-empty -m 'initial'
git rebase --onto newroot --root master
git branch -d newroot

(her şeyi tek bir yere koymak için)


Bu mükemmel. Git rebase -i --root'un dahili olarak yaptığı şey bu olsa iyi olurdu.
aredridel

Evet, bilmediğime şaşırdım.
Antony Hatchkins

git rebase newroot masterHata nedeniyle rebase komutunu değiştirmek zorunda kaldım .
marbel82

@ antony-hatchkins bunun için teşekkürler. Mevcut bir git repo var ve (ben buraya gitmek alışkanlık çeşitli nedenlerle) benim ilk taahhüt olarak bir BOŞ OLMAYAN git taahhüt eklemek için çalışıyorum. Bu yüzden git command --allow-empty -m 'initial' yerine git add; git commit -m "başlangıç ​​laravel taahhüdü"; git push; Ve sonra bu rebase adımı: git rebase --onto newroot --root master bir TON birleştirme çakışmasıyla başarısız oluyor. Herhangi bir tavsiye? : ((
kp123

@ kp123 boş bir taahhüt deneyin :)
Antony Hatchkins

12

Aristo'nun cevabını seviyorum. Ancak, büyük bir depo (> 5000 taahhüt) için filtre dalının çeşitli nedenlerden dolayı yeniden tabandan daha iyi çalıştığı bulundu 1) daha hızlı 2) birleştirme çatışması olduğunda insan müdahalesine ihtiyaç duymaz. 3) etiketleri yeniden yazabilir - koruyabilir. Filtre dalının çalıştığını unutmayın, çünkü her bir taahhüdün içeriği hakkında herhangi bir soru yoktur - bu 'rebase' öncesi ile tamamen aynıdır.

Adımlarım:

# first you need a new empty branch; let's call it `newroot`
git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d

# then you apply the same steps
git commit --allow-empty -m 'root commit'

# then use filter-branch to rebase everything on newroot
git filter-branch --parent-filter 'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat master

'--Tag-name-filter cat' seçeneklerinin, etiketlerin yeni oluşturulan taahhütlere işaret edecek şekilde yeniden yazılacağı anlamına geldiğini unutmayın.


Bu, aynı zamanda ilginç bir kullanım örneği olan boş olmayan taahhütler oluşturmaya yardımcı olmaz.
ceztko

Diğer çözümlerle karşılaştırıldığında, sizinki sadece bir önemsiz yan etkiye sahiptir: karmaları değiştirir, ancak tüm tarih dokunulmadan kalır. Teşekkür ederim!
Vladyslav Savchenko

5

Aristoteles'in ve Kent'in cevabının parçalarını başarıyla kullandım:

# first you need a new empty branch; let's call it `newroot`
git checkout --orphan newroot
git rm -rf .
git commit --allow-empty -m 'root commit'
git filter-branch --parent-filter \
'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat -- --all
# clean up
git checkout master
git branch -D newroot
# make sure your branches are OK first before this...
git for-each-ref --format="%(refname)" refs/original/ | \
xargs -n 1 git update-ref -d

Bu, masteretiketlere ek olarak tüm dalları da (sadece değil ) yeniden yazacaktır .


bu son satır ne yapıyor?
Diederick C.Niehorster

Her ref'yi arar refs/original/ve siler. Sildiği referanslara başka bir dal tarafından zaten referans verilmelidir, bu yüzden gerçekten gitmezler, sadece refs/original/kaldırılırlar.
ldav1s

Bu benim için çalıştı. Ayrıca kullandığım timedatectl set-time '2017-01-01 00:00:00'vermek newrooteski zaman damgası.
chrm

4

git rebase --root --onto $emptyrootcommit

hile kolayca yapmalı


3
$emptyrootcommithiçbir şeye genişleyen bir kabuk değişkeni nedir?
Flimm

@Flimm: $ emptyrootcommit, orijinal posterin zaten sahip olduğu boş bir taahhüdün sha1'i.
Uwe Kleine-König

4

Ben git replaceve git filter-branchkullanarak bir kullanmaktan daha iyi bir çözüm olduğunu düşünüyorum git rebase:

  • daha iyi performans
  • daha kolay ve daha az riskli (her adımda sonucunuzu doğrulayabilir ve yaptığınız işlemi geri alabilirsiniz ...)
  • garantili sonuçlarla birden fazla dalda iyi çalışır

Bunun arkasındaki fikir:

  • Geçmişte yeni bir boş taahhüt oluşturun
  • Eski root komutunu, yeni root komutunun bir üst öğe olarak eklenmesi dışında, tamamen benzer bir komutla değiştirin
  • Her şeyin beklendiği gibi olduğunu doğrulayın ve çalıştırın git filter-branch
  • Bir kez daha, her şeyin yolunda olduğunu doğrulayın ve daha fazla gitmeye gerek olmayan git dosyalarını temizleyin

İşte ilk 2 adım için bir komut dosyası:

#!/bin/bash
root_commit_sha=$(git rev-list --max-parents=0 HEAD)
git checkout --force --orphan new-root
find . -path ./.git -prune -o -exec rm -rf {} \; 2> /dev/null
git add -A
GIT_COMMITTER_DATE="2000-01-01T12:00:00" git commit --date==2000-01-01T12:00:00 --allow-empty -m "empty root commit"
new_root_commit_sha=$(git rev-parse HEAD)

echo "The commit '$new_root_commit_sha' will be added before existing root commit '$root_commit_sha'..."

parent="parent $new_root_commit_sha"
replacement_commit=$(
 git cat-file commit $root_commit_sha | sed "s/author/$parent\nauthor/" |
 git hash-object -t commit -w --stdin
) || return 3
git replace "$root_commit_sha" "$replacement_commit"

Bu komut dosyasını risksiz çalıştırabilirsiniz (daha önce hiç yapmadığınız bir eylemi gerçekleştirmeden önce yedekleme yapsanız bile iyi bir fikirdir;)) ve sonuç beklendiği gibi değilse, klasörde oluşturulan dosyaları silin .git/refs/replaceve tekrar deneyin; )

Deponun durumunun beklediğiniz gibi olduğunu doğruladıktan sonra, tüm dalların geçmişini güncellemek için aşağıdaki komutu çalıştırın :

git filter-branch -- --all

Şimdi, eski ve yeni olan 2 geçmiş görmelisiniz ( filter-branchdaha fazla bilgi için yardıma bakın ). 2'yi karşılaştırabilir ve her şeyin yolunda olup olmadığını tekrar kontrol edebilirsiniz. Memnun kalırsanız, artık gerekli olmayan dosyaları silin:

rm -rf ./.git/refs/original
rm -rf ./.git/refs/replace

Şubenize geri dönebilir masterve geçici şubeyi silebilirsiniz:

git checkout master
git branch -D new-root

Şimdi, her şey yapılmalıdır;)


3

Heyecanlandım ve bu güzel betiğin 'idempotent' bir versiyonunu yazdım ... her zaman aynı boş taahhüdü ekleyecek ve iki kez çalıştırırsanız, her seferinde kesin karmalarınızı değiştirmez. Yani, git-insert-empty-root almam :

#!/bin/sh -ev
# idempotence achieved!
tmp_branch=__tmp_empty_root
git symbolic-ref HEAD refs/heads/$tmp_branch
git rm --cached -r . || true
git clean -f -d
touch -d '1970-01-01 UTC' .
GIT_COMMITTER_DATE='1970-01-01T00:00:00 +0000' git commit \
  --date='1970-01-01T00:00:00 +0000' --allow-empty -m 'initial'
git rebase --committer-date-is-author-date --onto $tmp_branch --root master
git branch -d $tmp_branch

Ekstra karmaşıklığa değer mi? belki hayır, ama bunu kullanacağım.

Bu, aynı zamanda, repo'nun birkaç klonlanmış kopyasında bu işlemi gerçekleştirmenize ve aynı sonuçlarla sonuçlanmasına izin vermelidir, bu yüzden hala uyumludurlar ... test ... evet, çalışıyor, ancak silmeniz ve eklemeniz gerekiyor uzaktan kumandalar, örneğin:

git remote rm origin
git remote add --track master user@host:path/to/repo

3

Bir havuzun başına boş bir tamamlama eklemek için, "git init" ten hemen sonra boş bir tamamlama oluşturmayı unuttuysanız:

git rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904)

1
4b825dc ... boş ağacın
hashidir

2

Peki, işte ne buldum:

# Just setting variables on top for clarity.
# Set this to the path to your original repository.
ORIGINAL_REPO=/path/to/original/repository

# Create a new repository…
mkdir fun
cd fun
git init
# …and add an initial empty commit to it
git commit --allow-empty -m "The first evil."

# Add the original repository as a remote
git remote add previous $ORIGINAL_REPO
git fetch previous

# Get the hash for the first commit in the original repository
FIRST=`git log previous/master --pretty=format:%H  --reverse | head -1`
# Cherry-pick it
git cherry-pick $FIRST
# Then rebase the remainder of the original branch on top of the newly 
# cherry-picked, previously first commit, which is happily the second 
# on this branch, right after the empty one.
git rebase --onto master master previous/master

# rebase --onto leaves your head detached, I don't really know why)
# So now you overwrite your master branch with the newly rebased tree.
# You're now kinda done.
git branch -f master
git checkout master
# But do clean up: remove the remote, you don't need it anymore
git remote rm previous

2

İşte benim var bashdayalı komut Kent geliştirmelerle 'ın cevabı:

  • sadece masteryapıldığında değil, orijinal dalı kontrol eder ;
  • Geçici daldan kaçınmaya çalıştım, ancak git checkout --orphansadece bir dalla çalışıyor, müstakil devletle değil, bu yüzden yeni kök işlemek için yeterince uzun kontrol edildi ve sonra silindi;
  • sırasında yeni kök taahhüdünün karmasını kullanır filter-branch(Kent manuel değiştirme için orada bir yer tutucu bıraktı);
  • filter-branchoperasyon değil de uzaktakileri, sadece yerel şubesi yeniden yazar
  • yazar ve committer meta verileri standartlaştırılır, böylece kök taahhüdü depolar arasında aynı olur.

#!/bin/bash

# Save the current branch so we can check it out again later
INITIAL_BRANCH=`git symbolic-ref --short HEAD`
TEMP_BRANCH='newroot'

# Create a new temporary branch at a new root, and remove everything from the tree
git checkout --orphan "$TEMP_BRANCH"
git rm -rf .

# Commit this empty state with generic metadata that will not change - this should result in the same commit hash every time
export GIT_AUTHOR_NAME='nobody'
export GIT_AUTHOR_EMAIL='nobody@example.org'
export GIT_AUTHOR_DATE='2000-01-01T00:00:00+0000'
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
git commit --allow-empty -m 'empty root'
NEWROOT=`git rev-parse HEAD`

# Check out the commit we just made and delete the temporary branch
git checkout --detach "$NEWROOT"
git branch -D "$TEMP_BRANCH"

# Rewrite all the local branches to insert the new root commit, delete the 
# original/* branches left behind, and check out the rewritten initial branch
git filter-branch --parent-filter "sed \"s/^\$/-p $NEWROOT/\"" --tag-name-filter cat -- --branches
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
git checkout "$INITIAL_BRANCH"

2

Kök taahhüdünü değiştirmek için:

İlk olarak, ilk olarak istediğiniz taahhüdü oluşturun.

İkinci olarak, taahhütlerin sırasını aşağıdakileri kullanarak değiştirin:

git rebase -i --kök

Kök kesinleşene kadar taahhütlerle birlikte bir editör görünür:

1234 eski kök mesajı seç

0294 ortada bir taahhüt seç

Köke koymak istediğiniz 5678 taahhüdü seçin

Daha sonra, ilk satıra yerleştirerek önce istediğiniz taahhüdü koyabilirsiniz. Örnekte:

Köke koymak istediğiniz 5678 taahhüdü seçin

1234 eski kök mesajı seç

0294 ortada bir taahhüt seç

Düzenleyiciden çıkın ve taahhüt siparişi değişti.

Not: Git'in düzenleyicisini değiştirmek için şunu çalıştırın:

git config --global core.editor name_of_the_editor_program_you_want_to_use


1
Şimdi rebase --root'a sahip, bu en güzel çözüm.
Ross Burton

1

En son ve en iyiyi birleştiriyoruz. Yan etki yok, çakışma yok, etiketleri tutuyor.

git log --reverse

tree=`git hash-object -wt tree --stdin < /dev/null`
commit=`git commit-tree -m 'Initialize empty repository' $tree`
echo $commit # copy below, interpolation didn't work for me

git filter-branch --parent-filter 'sed "s/^\$/-p <commit>/"' --tag-name-filter cat master

git log --reverse

GitHub'da CI çalıştırma verilerini kaybedeceğinizi ve diğer şubeler de düzeltilmedikçe PR'nin dağıtılabileceğini unutmayın.


0

Aşağıdaki cevap Aristoteles Pagaltzis ve diğerleri ama daha basit komutlar kullanarak

zsh% git checkout --orphan empty     
Switched to a new branch 'empty'
zsh% git rm --cached -r .
zsh% git clean -fdx
zsh% git commit --allow-empty -m 'initial empty commit'
[empty (root-commit) 64ea894] initial empty commit
zsh% git checkout master
Switched to branch 'master'
zsh% git rebase empty
First, rewinding head to replay your work on top of it...
zsh% git branch -d empty 
Deleted branch empty (was 64ea894).

Repo'nuzun yapılmayı bekleyen hiçbir yerel değişiklik içermemesi gerektiğini unutmayın.
Not git checkout --orphangit yeni sürümlerinde çalışacağını tahmin ediyorum.
Çoğu zaman git statusyararlı ipuçları verdiğine dikkat edin.


-6

Yeni bir depo başlatın.

Tarihinizi istediğiniz başlangıç ​​tarihine ayarlayın.

Her şeyi istediğiniz gibi yapın, sistem saatini istediğiniz zaman istediğiniz şekilde yansıtacak şekilde ayarlayın. Çok fazla gereksiz yazmayı önlemek için dosyaları mevcut depodan gerektiği gibi çekin.

Bugüne geldiğinizde depoları değiştirin ve işiniz bitti.

Eğer sadece çılgın (yerleşik) ama makul derecede zekiyseniz (muhtemelen, bunun gibi çılgın fikirleri düşünmek için belirli miktarda akıllıya sahip olmanız gerektiğinden) süreci yazacaksınız.

Bu, geçmişin bir hafta sonra başka bir şekilde olmasını istediğinize karar verdiğinizde de daha güzel olacaktır.


Sistem tarihi ile uğraşmanızı gerektiren bir çözüm hakkında kötü hislerim var, ama bana bir fikir verdiniz, ki biraz geliştirdim ve ne yazık ki işe yaradı. Çok teşekkürler.
kch

-7

Bu yazı eski biliyorum ama bu sayfa Google "ekleyerek git git" zaman ilk sayfasıdır.

Neden basit şeyleri karmaşık hale getirelim?

ABC'niz var ve ABZC istiyorsunuz.

  1. git rebase -i trunk (veya B'den önceki herhangi bir şey)
  2. B satırında düzenlenecek seçimi değiştir
  3. değişikliklerinizi yapın: git add ..
  4. git commit( git commit --amendB'yi düzenler ve Z oluşturmaz)

[Daha fazla işlem git commiteklemek için burada istediğiniz kadar yapabilirsiniz . Tabii ki, adım 5 ile ilgili sorunlarınız olabilir, ancak git ile birleşme çatışmasını çözmek, sahip olmanız gereken bir beceridir. Değilse, pratik yapın!]

  1. git rebase --continue

Basit, değil mi?

Eğer anlarsan git rebase , bir 'kök' taahhüdü eklemek sorun olmamalı.

Git ile eğlenin!


5
Soru ilk taahhüdü eklemenizi ister: ABC'den ZABC'yi istiyorsunuz. Basit bir git rebaseşey bunu yapamaz.
Petr Viktorin
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.