Làm cho cam kết hiện tại là cam kết duy nhất (ban đầu) trong kho Git?


664

Tôi hiện có một kho lưu trữ Git cục bộ, mà tôi đẩy đến một kho lưu trữ Github.

Kho lưu trữ cục bộ có ~ 10 lần xác nhận và kho lưu trữ Github là bản sao được đồng bộ hóa của điều này.

Những gì tôi muốn làm là xóa TẤT CẢ lịch sử phiên bản khỏi kho Git cục bộ, do đó, nội dung hiện tại của kho lưu trữ xuất hiện dưới dạng cam kết duy nhất (và do đó các phiên bản cũ hơn của tệp trong kho không được lưu trữ).

Sau đó tôi muốn đẩy những thay đổi này đến Github.

Tôi đã điều tra Git rebase, nhưng điều này có vẻ phù hợp hơn để loại bỏ các phiên bản cụ thể. Một giải pháp tiềm năng khác là xóa repo cục bộ và tạo một cái mới - mặc dù điều này có thể sẽ tạo ra rất nhiều công việc!

ETA: Có các thư mục / tệp cụ thể không bị theo dõi - nếu có thể tôi muốn duy trì việc không theo dõi các tệp này.


6
Xem thêm stackoverflow.com/questions/435646/ ((Làm cách nào để kết hợp hai cam kết đầu tiên của kho lưu trữ Git? ")
Anonymoose


Câu trả lời:


981

Đây là cách tiếp cận vũ phu. Nó cũng loại bỏ cấu hình của kho lưu trữ.

Lưu ý : Điều này KHÔNG hoạt động nếu kho lưu trữ có các mô hình con! Nếu bạn đang sử dụng mô hình con, bạn nên sử dụng, vd rebase tương tác

Bước 1: xóa tất cả lịch sử ( Đảm bảo bạn đã sao lưu, điều này không thể được hoàn nguyên )

cat .git/config  # note <github-uri>
rm -rf .git

Bước 2: xây dựng lại repo Git chỉ với nội dung hiện tại

git init
git add .
git commit -m "Initial commit"

Bước 3: đẩy đến GitHub.

git remote add origin <github-uri>
git push -u --force origin master

3
Cảm ơn larsmans - Tôi đã chọn sử dụng điều này như là giải pháp của tôi. Mặc dù việc khởi tạo repo Git làm mất bản ghi của các tệp không bị theo dõi trong repo cũ, đây có lẽ là một giải pháp đơn giản hơn cho vấn đề của tôi.
kaese

5
@kaese: Tôi nghĩ bạn .gitignorenên xử lý chúng, phải không?
Fred Foo

48
Lưu .git / config của bạn trước và khôi phục nó sau.
lalebarde

@lalebarde Nếu bạn khôi phục .git / config sau git commit -m "Initial commit"đó, bạn có thể bỏ qua git remote add ...phần đó, giả sử rằng đã có trong cấu hình của bạn và chuyển thẳng sang đẩy. Nó làm việc cho tôi.
Butussy Butkus

24
Hãy cẩn thận với điều này nếu bạn đang cố xóa dữ liệu nhạy cảm: sự hiện diện của một cam kết duy nhất trong nhánh chính mới được đẩy là sai lệch - lịch sử sẽ vẫn tồn tại mà nó không thể truy cập được từ nhánh đó. Nếu bạn có thẻ, ví dụ, trỏ đến các xác nhận cũ hơn, các cam kết này sẽ có thể truy cập được. Trên thực tế, đối với bất kỳ ai có một chút git foo, tôi chắc chắn rằng sau lần đẩy git này, họ vẫn có thể khôi phục tất cả lịch sử từ kho lưu trữ GitHub - và nếu bạn có các nhánh hoặc thẻ khác, thì họ sẽ không thậm chí cần nhiều git foo.
Robert Muil

620

Giải pháp duy nhất phù hợp với tôi (và giữ cho các mô đun con hoạt động) là

git checkout --orphan newBranch
git add -A  # Add all files and commit them
git commit
git branch -D master  # Deletes the master branch
git branch -m master  # Rename the current branch to master
git push -f origin master  # Force push master branch to github
git gc --aggressive --prune=all     # remove the old files

Xóa .git/luôn gây ra vấn đề lớn khi tôi có mô hình con. Sử dụng git rebase --rootbằng cách nào đó sẽ gây ra xung đột cho tôi (và mất nhiều thời gian vì tôi có rất nhiều lịch sử).


54
Đây phải là câu trả lời chính xác! chỉ cần thêm một git push -f origin masterop cuối cùng và mặt trời sẽ lại tỏa sáng trên repo tươi của bạn! :)
gru

2
Điều này không giữ những cam kết cũ xung quanh?
Brad

4
@JonePolvora git lấy; thiết lập lại git - gốc nguồn / chủ stackoverflow.com/questions/4785107/NH
echo

5
Sau khi làm điều này, repo sẽ có không gian trống?
Inuart

8
Tôi tin rằng bạn nên thêm đề xuất của @JasonGoemaat làm dòng cuối cùng cho câu trả lời của bạn. Nếu không có git gc --aggressive --prune alltoàn bộ điểm mất lịch sử sẽ bị bỏ lỡ.
Tuncay Göncüoğlu

93

Đây là cách tiếp cận ưa thích của tôi:

git branch new_branch_name $(echo "commit message" | git commit-tree HEAD^{tree})

Điều này sẽ tạo ra một nhánh mới với một cam kết bổ sung mọi thứ trong ĐẦU. Nó không thay đổi bất cứ điều gì khác, vì vậy nó hoàn toàn an toàn.


3
Cách tiếp cận tốt nhất! Rõ ràng, và làm công việc. Ngoài ra, tôi đổi tên chi nhánh với rất nhiều thay đổi từ "chính" thành "công việc cục bộ" và "new_branch_name" thành "chính chủ". Trong master, hãy làm như sau: git -m local-thay đổi chi nhánh git -m local-thay đổi git checkout new_branch_name git chi nhánh -m master <
Valtoni Boaventura 9/2/2015

Cái này trông rất ngắn và bóng bẩy, điều duy nhất tôi chưa hiểu hoặc chưa thấy là ĐẦU ^ {cây}, ai đó có thể giải thích? Ngoài ra, tôi đã đọc điều này là "tạo chi nhánh mới từ cam kết đã cho, được tạo bằng cách tạo đối tượng cam kết mới với thông báo cam kết đã cho từ ___"
TomKeegasi

3
Vị trí cuối cùng để tìm câu trả lời cho các câu hỏi về cú pháp tham chiếu git là trong các git-rev-parsetài liệu. Những gì đang xảy ra ở đây git-commit-treeđòi hỏi một tham chiếu đến một cái cây (ảnh chụp nhanh của repo), nhưng HEADlà một bản sửa đổi. Để tìm cây liên kết với một cam kết, chúng tôi sử dụng <rev>^{<type>}mẫu.
dan_waterworth

Câu trả lời tốt đẹp. Hoạt động tốt. Cuối cùng hãy nóigit push --force <remote> new_branch_name:<remote-branch>
Felipe Alvarez

31

Tùy chọn khác, có thể trở thành rất nhiều công việc nếu bạn có nhiều cam kết, là một rebase tương tác (giả sử phiên bản git của bạn là> = 1.7.12):git rebase --root -i

Khi được trình bày với một danh sách các cam kết trong trình soạn thảo của bạn:

  • Thay đổi "chọn" thành "tua lại" cho lần xác nhận đầu tiên
  • Thay đổi "chọn" thành "sửa lỗi" mọi cam kết khác

Lưu và đóng. Git sẽ bắt đầu nổi loạn.

Cuối cùng, bạn sẽ có một cam kết gốc mới là sự kết hợp của tất cả các cam kết đi sau nó.

Ưu điểm là bạn không phải xóa kho lưu trữ của mình và nếu bạn có suy nghĩ thứ hai, bạn luôn có một dự phòng.

Nếu bạn thực sự muốn nuke lịch sử của mình, hãy đặt lại master thành cam kết này và xóa tất cả các nhánh khác.


Sau khi rebase hoàn thành, tôi không thể đẩy:error: failed to push some refs to
Begueradj

@Begueradj nếu bạn đã đẩy chi nhánh bạn đã khởi động, thì bạn sẽ cần phải đẩy git push --force-with-lease. buộc-cho thuê được sử dụng vì nó ít phá hủy hơn - lực lượng.
Carl

19

Biến thể của larsmans phương pháp đề xuất :

Lưu danh sách không theo dõi của bạn:

git ls-files --others --exclude-standard > /tmp/my_untracked_files

Lưu cấu hình git của bạn:

mv .git/config /tmp/

Sau đó thực hiện các bước đầu tiên của larsmans:

rm -rf .git
git init
git add .

Khôi phục cấu hình của bạn:

mv /tmp/config .git/

Unrack bạn không bị theo dõi các tập tin:

cat /tmp/my_untracked_files | xargs -0 git rm --cached

Sau đó cam kết:

git commit -m "Initial commit"

Và cuối cùng đẩy vào kho lưu trữ của bạn:

git push -u --force origin master

6

Dưới đây là một kịch bản được điều chỉnh từ câu trả lời của @Zeelot. Nó sẽ xóa lịch sử khỏi tất cả các nhánh, không chỉ nhánh chính:

for BR in $(git branch); do   
  git checkout $BR
  git checkout --orphan ${BR}_temp
  git commit -m "Initial commit"
  git branch -D $BR
  git branch -m $BR
done;
git gc --aggressive --prune=all

Nó hoạt động cho mục đích của tôi (tôi không sử dụng mô hình con).


4
Tôi nghĩ rằng bạn đã quên buộc chủ đẩy hoàn thành thủ tục.
not2qubit

2
Tôi đã phải thực hiện một sửa đổi nhỏ. git branchsẽ bao gồm một dấu sao bên cạnh nhánh đã kiểm tra của bạn, sau đó sẽ được đặt toàn cầu, khiến nó giải quyết tất cả các tệp hoặc thư mục như thể chúng cũng là tên nhánh. Thay vào đó, tôi sử dụng git branch --format="%(refname:lstrip=2)"mà chỉ cho tôi tên chi nhánh.
Ben Richards

@ not2qubit: Cảm ơn vì điều này. Điều gì sẽ là lệnh chính xác? git push --force origin masterHoặc git push --force-with-lease? Rõ ràng cái sau an toàn hơn (xem stackoverflow.com/questions/5509543/ ))
Shafique Jamal

@BenRichards. Hấp dẫn. Tôi sẽ thử lại điều này tại một số điểm với một thư mục khớp với tên chi nhánh để kiểm tra nó, sau đó cập nhật câu trả lời. Cảm ơn.
Shafique Jamal

5

Bạn có thể sử dụng bản sao nông (git> 1.9):

git clone --depth depth remote-url

Đọc thêm: http://bloss.atlassian.com/2014/05/handle-big-reposeocate-git/


4
Bản sao như vậy không thể được đẩy đến một kho lưu trữ mới.
Seweryn Niemiec

1
Sẽ rất hữu ích khi biết cách phá vỡ giới hạn đó. Ai đó có thể giải thích tại sao điều này không thể được thúc đẩy?
not2qubit

Câu trả lời cho câu hỏi của bạn: stackoverflow.com/questions/6900103/ từ
Matthias M

4

git filter-branch là công cụ phẫu thuật lớn.

git filter-branch --parent-filter true -- @^!

--parent-filterđược cha mẹ trên stdin và nên in cha mẹ viết lại trên thiết bị xuất chuẩn; unix truethoát thành công và không in gì, vì vậy: không có cha mẹ. @^!viết tắt của Git cho "cam kết đầu nhưng không phải bất kỳ cha mẹ nào của nó". Sau đó xóa tất cả các ref khác và đẩy lúc rảnh rỗi.


3

Chỉ cần xóa repo Github và tạo một cái mới. Cho đến nay cách tiếp cận nhanh nhất, dễ dàng nhất và an toàn nhất. Rốt cuộc, bạn phải làm gì để thực hiện tất cả các lệnh đó trong giải pháp được chấp nhận khi tất cả những gì bạn muốn là nhánh chính với một cam kết duy nhất?


1
Một trong những điểm chính là có thể thấy nó được rẽ nhánh từ đâu.
not2qubit

Tôi mới làm điều này và nó vẫn ổn
thanos.a

2

Phương pháp bên dưới hoàn toàn có thể tái tạo, do đó không cần phải chạy lại bản sao nếu cả hai bên đều nhất quán, chỉ cần chạy tập lệnh ở phía bên kia.

git log -n1 --format=%H >.git/info/grafts
git filter-branch -f
rm .git/info/grafts

Nếu sau đó bạn muốn dọn sạch nó, hãy thử tập lệnh này:

http://sam.nipl.net/b/git-gc-all-ferocious

Tôi đã viết một tập lệnh "giết lịch sử" cho mỗi nhánh trong kho lưu trữ:

http://sam.nipl.net/b/git-kill-history

xem thêm: http://sam.nipl.net/b/ xác nhận


1
Cám ơn vì cái này. Chỉ cần FYI: tập lệnh của bạn để giết lịch sử cho mỗi chi nhánh có thể sử dụng một số cập nhật - nó đưa ra các lỗi sau: git-hash: not foundSupport for <GIT_DIR>/info/grafts is deprecated
Shafique Jamal

1
@ShafiqueJamal, cảm ơn, tập lệnh "git-hash" nhỏ git log HEAD~${1:-0} -n1 --format=%H, ở đây, sam.aiki.info/b/git-hash Sẽ tốt hơn nếu đặt tất cả trong một tập lệnh cho tiêu dùng công cộng. Nếu tôi sử dụng nó một lần nữa, tôi có thể tìm ra cách thực hiện với tính năng mới thay thế "ghép".
Sam Watkins

2

Những gì tôi muốn làm là xóa TẤT CẢ lịch sử phiên bản khỏi kho Git cục bộ, do đó, nội dung hiện tại của kho lưu trữ xuất hiện dưới dạng cam kết duy nhất (và do đó các phiên bản cũ hơn của tệp trong kho không được lưu trữ).

Một câu trả lời khái niệm hơn:

git rác tự động thu thập các xác nhận cũ nếu không có thẻ / nhánh / refs trỏ đến chúng. Vì vậy, bạn chỉ cần xóa tất cả các thẻ / chi nhánh và tạo một cam kết mồ côi mới, được liên kết với bất kỳ chi nhánh nào - theo quy ước, bạn sẽ để chi nhánh mastertrỏ đến cam kết đó.

Các cam kết cũ, không thể truy cập sau đó sẽ không bao giờ được nhìn thấy bởi bất cứ ai trừ khi họ đi đào với các lệnh git cấp thấp. Nếu điều đó là đủ cho bạn, tôi sẽ chỉ dừng lại ở đó và để cho GC tự động thực hiện công việc đó bất cứ khi nào nó muốn. Nếu bạn muốn loại bỏ chúng ngay lập tức, bạn có thể sử dụng git gc(có thể với --aggressive --prune=all). Đối với kho git từ xa, không có cách nào để bạn buộc điều đó, trừ khi bạn có quyền truy cập shell vào hệ thống tệp của họ.


Ngoài ra, khi nhìn thấy trong câu trả lời của @Zeelot.
Mogens TrasherDK

Yup, Zeelot có các lệnh về cơ bản thực hiện điều này (chỉ khác, bằng cách bắt đầu lại hoàn toàn, điều này có thể tốt cho OP). @MogensTrasherDK
AnoE

0

Ở đây bạn đi:

#!/bin/bash
#
# By Zibri (2019)
#
# Usage: gitclean username password giturl
#
gitclean () 
{ 
    odir=$PWD;
    if [ "$#" -ne 3 ]; then
        echo "Usage: gitclean username password giturl";
        return 1;
    fi;
    temp=$(mktemp -d 2>/dev/null /dev/shm/git.XXX || mktemp -d 2>/dev/null /tmp/git.XXX);
    cd "$temp";
    url=$(echo "$3" |sed -e "s/[^/]*\/\/\([^@]*@\)\?\.*/\1/");
    git clone "https://$1:$2@$url" && { 
        cd *;
        for BR in "$(git branch|tr " " "\n"|grep -v '*')";
        do
            echo working on branch $BR;
            git checkout $BR;
            git checkout --orphan $(basename "$temp"|tr -d .);
            git add -A;
            git commit -m "Initial Commit" && { 
                git branch -D $BR;
                git branch -m $BR;
                git push -f origin $BR;
                git gc --aggressive --prune=all
            };
        done
    };
    cd $odir;
    rm -rf "$temp"
}

Cũng được lưu trữ tại đây: https://gist.github.com/Zibri/76614988478a076bbe105545a16ee743


Trời ạ! Đừng bắt tôi cung cấp mật khẩu không được bảo vệ, không được bảo vệ của tôi tại dòng lệnh! Ngoài ra, đầu ra của nhánh git thường không phù hợp cho kịch bản. Bạn có thể muốn xem các công cụ hệ thống ống nước.
D. Ben Knoble

-1

Tôi đã giải quyết vấn đề tương tự bằng cách xóa .gitthư mục khỏi dự án của mình và tái hòa nhập với kiểm soát phiên bản thông qua IntelliJ. Lưu ý: .gitThư mục bị ẩn. Bạn có thể xem nó trong thiết bị đầu cuối với ls -a, và sau đó loại bỏ nó bằng cách sử dụng rm -rf .git.


đó là những gì anh ấy đang làm trong bước 1: rm -rf .git?
đêm ngày

-1

Đối với việc sử dụng lệnh Shallow Clone git clone --depth 1 URL - Nó sẽ chỉ nhân bản CHÍNH hiện tại của kho lưu trữ


-2

Để xóa cam kết cuối cùng khỏi git, bạn chỉ cần chạy

git reset --hard HEAD^ 

Nếu bạn đang xóa nhiều xác nhận từ đầu, bạn có thể chạy

git reset --hard HEAD~2 

để loại bỏ hai cam kết cuối cùng. Bạn có thể tăng số lượng để loại bỏ nhiều cam kết hơn.

Thêm thông tin ở đây.

Git tutoturial ở đây cung cấp trợ giúp về cách thanh lọc kho lưu trữ:

bạn muốn xóa tệp khỏi lịch sử và thêm nó vào .gitignore để đảm bảo nó không vô tình được tái cam kết. Ví dụ, chúng tôi sẽ xóa Rakefile khỏi kho đá quý GitHub.

git clone https://github.com/defunkt/github-gem.git

cd github-gem

git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch Rakefile' \
  --prune-empty --tag-name-filter cat -- --all

Bây giờ chúng tôi đã xóa tệp khỏi lịch sử, hãy đảm bảo rằng chúng tôi không vô tình cam kết lại.

echo "Rakefile" >> .gitignore

git add .gitignore

git commit -m "Add Rakefile to .gitignore"

Nếu bạn hài lòng với trạng thái của kho lưu trữ, bạn cần phải đẩy các thay đổi để ghi đè lên kho lưu trữ từ xa.

git push origin master --force

6
Xóa tệp hoặc xác nhận khỏi kho lưu trữ hoàn toàn không liên quan đến câu hỏi (yêu cầu xóa lịch sử, một điều hoàn toàn khác). OP muốn có một lịch sử rõ ràng nhưng muốn duy trì trạng thái hiện tại của kho lưu trữ.
Victor Schröder

Điều này không tạo ra kết quả được hỏi trong câu hỏi. bạn đang loại bỏ tất cả các thay đổi sau khi cam kết bạn giữ lần cuối và mất tất cả các thay đổi kể từ đó, nhưng câu hỏi yêu cầu giữ các tệp hiện tại và bỏ lịch sử.
Tuncay Göncüoğlu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.