Làm cách nào để thay đổi tên tác giả và tên người gửi và e-mail của nhiều lần xác nhận trong Git?


2392

Tôi đã viết một kịch bản đơn giản trong máy tính của trường và cam kết thay đổi Git (trong một bản repo trong ổ đĩa của tôi, được nhân bản từ máy tính của tôi ở nhà). Sau một vài lần xác nhận, tôi nhận ra rằng tôi đã cam kết công cụ là người dùng root.

Có cách nào để thay đổi tác giả của những cam kết này thành tên của tôi không?


13
Câu hỏi: việc sử dụng nhánh bộ lọc git có bảo vệ SHA1 cho các thẻ, phiên bản và đối tượng trước đó không? Hay việc thay đổi tên tác giả cũng sẽ thay đổi SHA1 liên quan?
AndyL

36
Băm sẽ thay đổi có
Không có sẵn

3
Tiếp theo, tôi đã tạo ra một kịch bản nhỏ mà cuối cùng đã khắc phục được nguyên nhân gốc rễ cho tôi. gist.github.com/tripleee/16767aa4137706fd896c
tripleee

2
@impinball Độ tuổi của câu hỏi hầu như không liên quan. Tạo một câu hỏi trùng lặp mới là ra khỏi câu hỏi. Tôi cho rằng tôi có thể tạo ra một câu hỏi đặt ra câu trả lời cụ thể này nhưng tôi không hoàn toàn tin rằng nó sẽ có được nhiều tầm nhìn như vậy. Không giống như thiếu câu hỏi Git ở đây ... Dù sao tôi cũng có thể giúp.
tripleee

8
GitHub có kịch bản đặc biệt cho việc này: help.github.com/articles/changing- mượt
Timur Bernikovich

Câu trả lời:


1214

Câu trả lời này sử dụng git-filter-branch, mà các tài liệu hiện đưa ra cảnh báo này:

nhánh bộ lọc git có rất nhiều cạm bẫy có thể tạo ra các luồng không rõ ràng của lịch sử dự định viết lại (và có thể khiến bạn mất ít thời gian để điều tra các vấn đề như vậy vì nó có hiệu suất khủng khiếp như vậy). Các vấn đề về an toàn và hiệu suất này không thể được khắc phục một cách tương thích và do đó, việc sử dụng nó không được khuyến khích. Vui lòng sử dụng một công cụ lọc lịch sử thay thế như git filter-repo . Nếu bạn vẫn cần sử dụng nhánh lọc git, vui lòng đọc kỹ AN TOÀN (và HIỆU SUẤT ) để tìm hiểu về các mỏ đất của nhánh lọc, sau đó thận trọng tránh càng nhiều mối nguy được liệt kê ở đó càng tốt.

Thay đổi tác giả (hoặc người đi làm) sẽ yêu cầu viết lại tất cả lịch sử. Nếu bạn ổn với điều đó và nghĩ rằng nó đáng giá thì bạn nên kiểm tra nhánh lọc git . Trang người đàn ông bao gồm một số ví dụ để giúp bạn bắt đầu. Cũng lưu ý rằng bạn có thể sử dụng các biến môi trường để thay đổi tên của tác giả, người đăng ký, ngày tháng, v.v. - xem phần "Biến môi trường" trên trang git man .

Cụ thể, bạn có thể sửa tất cả tên tác giả và email sai cho tất cả các nhánh và thẻ bằng lệnh này (nguồn: GitHub trợ giúp ):

#!/bin/sh

git filter-branch --env-filter '
OLD_EMAIL="your-old-email@example.com"
CORRECT_NAME="Your Correct Name"
CORRECT_EMAIL="your-correct-email@example.com"
if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_COMMITTER_NAME="$CORRECT_NAME"
    export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_AUTHOR_NAME="$CORRECT_NAME"
    export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
fi
' --tag-name-filter cat -- --branches --tags

612
Github có một tập lệnh công khai cho help.github.com/articles/changing- mượt-info và nó hoạt động rất tốt!
defvol

34
Sau khi thực thi tập lệnh, bạn có thể xóa nhánh sao lưu bằng cách thực hiện "git update-ref -d refs / original / refs / Heads / master".
DR

7
@rodowi, nó sao chép tất cả các cam kết của tôi.
Rafael Barros

6
@RafaelBarros thông tin tác giả (giống như mọi thứ khác trong lịch sử) là một phần của khóa sha của cam kết. Mọi thay đổi đối với lịch sử là viết lại dẫn đến id mới cho tất cả các cam kết. Vì vậy, đừng viết lại trên một repo được chia sẻ hoặc đảm bảo tất cả người dùng đều biết về nó ...
johannes

20
Đã giải quyết bằng cách sử dụnggit push --force --tags origin HEAD:master
mcont

1577

LƯU Ý: Câu trả lời này thay đổi SHA1, vì vậy hãy cẩn thận khi sử dụng nó trên một nhánh đã được đẩy. Nếu bạn chỉ muốn sửa lỗi chính tả của một tên hoặc cập nhật một email cũ, git cho phép bạn làm điều này mà không cần viết lại lịch sử bằng cách sử dụng .mailmap. Xem câu trả lời khác của tôi .

Sử dụng Rebase tương tác

Bạn có thể làm

git rebase -i -p <some HEAD before all of your bad commits>

Sau đó đánh dấu tất cả các cam kết xấu của bạn là "chỉnh sửa" trong tệp rebase. Nếu bạn cũng muốn thay đổi cam kết đầu tiên của mình, bạn phải thêm thủ công dưới dạng dòng đầu tiên trong tệp rebase (theo định dạng của các dòng khác). Sau đó, khi git yêu cầu bạn sửa đổi từng cam kết, hãy làm

 git commit --amend --author "New Author Name <email@address.com>" 

chỉnh sửa hoặc chỉ đóng trình chỉnh sửa mở ra, sau đó làm

git rebase --continue

để tiếp tục cuộc nổi loạn.

Bạn có thể bỏ qua việc mở trình soạn thảo hoàn toàn ở đây bằng cách nối thêm --no-edit để lệnh sẽ là:

git commit --amend --author "New Author Name <email@address.com>" --no-edit && \
git rebase --continue

Cam kết duy nhất

Như một số người bình luận đã lưu ý, nếu bạn chỉ muốn thay đổi cam kết gần đây nhất, lệnh rebase là không cần thiết. Cứ làm đi

 git commit --amend --author "New Author Name <email@address.com>"

Điều này sẽ thay đổi tác giả thành tên được chỉ định, nhưng committer sẽ được đặt thành người dùng được cấu hình của bạn trong git config user.namegit config user.email. Nếu bạn muốn đặt committer thành thứ gì đó bạn chỉ định, điều này sẽ đặt cả tác giả và committer:

 git -c user.name="New Author Name" -c user.email=email@address.com commit --amend --reset-author

Lưu ý về Cam kết hợp nhất

Có một lỗ hổng nhỏ trong phản ứng ban đầu của tôi. Nếu có bất kỳ cam kết hợp nhất nào giữa hiện tại HEADvà của bạn <some HEAD before all your bad commits>, thì git rebasesẽ làm phẳng chúng (và nhân tiện, nếu bạn sử dụng các yêu cầu kéo GitHub, sẽ có một tấn cam kết hợp nhất trong lịch sử của bạn). Điều này rất thường có thể dẫn đến lịch sử rất khác nhau (vì các thay đổi trùng lặp có thể bị "loại bỏ") và trong trường hợp xấu nhất, nó có thể dẫn đến git rebaseyêu cầu bạn giải quyết xung đột hợp nhất khó khăn (có thể đã được giải quyết trong các cam kết hợp nhất). Giải pháp là sử dụng -pcờ để git rebasebảo tồn cấu trúc hợp nhất của lịch sử của bạn. Trang web git rebasecảnh báo rằng việc sử dụng -p-icó thể dẫn đến các vấn đề, nhưng trongBUGS phần có nội dung "Chỉnh sửa cam kết và viết lại thông điệp cam kết của họ sẽ hoạt động tốt."

Tôi đã thêm vào -plệnh trên. Đối với trường hợp bạn chỉ thay đổi cam kết gần đây nhất, đây không phải là vấn đề.


27
Tuyệt vời cho cam kết kỳ quặc - hữu ích nếu bạn đang ghép nối và quên thay đổi tác giả
mloughran

32
+1 để đề cập đến usecase cho cách sửa lỗi một lỗi điển hình: git commit --amend - Tác giả = tên người dùng
Nathan Kidd

12
Điều này là hoàn hảo, usecase phổ biến nhất của tôi là tôi ngồi xuống một máy tính khác và quên thiết lập tác giả và do đó thường có <5 cam kết hoặc lâu hơn để khắc phục.
Zitrax

57
git commit --amend --reset-authorcũng hoạt động một lần user.nameuser.emailđược cấu hình chính xác.
pts

14
Viết lại thông tin tác giả trên tất cả các cam kết sau khi <commit>sử dụng user.nameuser.emailtừ ~/.gitconfig: chạy git rebase -i <commit> --exec 'git commit --amend --reset-author --no-edit', lưu, thoát. Không cần chỉnh sửa!
ntc2

588

Bạn cũng có thể làm:

git filter-branch --commit-filter '
        if [ "$GIT_COMMITTER_NAME" = "<Old Name>" ];
        then
                GIT_COMMITTER_NAME="<New Name>";
                GIT_AUTHOR_NAME="<New Name>";
                GIT_COMMITTER_EMAIL="<New Email>";
                GIT_AUTHOR_EMAIL="<New Email>";
                git commit-tree "$@";
        else
                git commit-tree "$@";
        fi' HEAD

Lưu ý, nếu bạn đang sử dụng lệnh này trong dấu nhắc lệnh của Windows, thì bạn cần sử dụng "thay vì ':

git filter-branch --commit-filter "
        if [ "$GIT_COMMITTER_NAME" = "<Old Name>" ];
        then
                GIT_COMMITTER_NAME="<New Name>";
                GIT_AUTHOR_NAME="<New Name>";
                GIT_COMMITTER_EMAIL="<New Email>";
                GIT_AUTHOR_EMAIL="<New Email>";
                git commit-tree "$@";
        else
                git commit-tree "$@";
        fi" HEAD

4
Không sử dụng bộ lọc env là giải pháp dễ dàng hơn? Không chắc chắn tại sao điều này nhận được nhiều phiếu hơn, sau đó.
stigkj

3
Sau đó liên kết bị hỏng. Làm thế nào để chúng ta đẩy những thay đổi này sang kho lưu trữ khác?
Russell

28
env-filter sẽ thay đổi tất cả các cam kết. Giải pháp này cho phép một điều kiện.
dùng208769

5
"A previous backup already exists in refs/original/ Force overwriting the backup with -f"xin lỗi, nhưng nơi -f-flag sẽ diễn ra khi thực hiện kịch bản này hai lần. Thật ra đó là câu trả lời của Brian, xin lỗi về sự xáo trộn ngay sau khi nhánh lọc là giải pháp.
hhh

2
@ user208769 env-filter cũng cho phép có điều kiện; nhìn vào câu trả lời của tôi :-)
stigkj

559

Một lớp lót, nhưng hãy cẩn thận nếu bạn có một kho lưu trữ nhiều người dùng - điều này sẽ thay đổi tất cả các cam kết để có cùng tác giả (người mới) và người đi làm.

git filter-branch -f --env-filter "GIT_AUTHOR_NAME='Newname'; GIT_AUTHOR_EMAIL='new@email'; GIT_COMMITTER_NAME='Newname'; GIT_COMMITTER_EMAIL='new@email';" HEAD

Với ngắt dòng trong chuỗi (có thể có trong bash):

git filter-branch -f --env-filter "
    GIT_AUTHOR_NAME='Newname'
    GIT_AUTHOR_EMAIL='new@email'
    GIT_COMMITTER_NAME='Newname'
    GIT_COMMITTER_EMAIL='new@email'
  " HEAD

Điểm nhỏ, xuất khẩu thực sự là thừa mặc dù nó không có hại. ví dụ: git-filter-Branch --env-filter "GIT_AUTHOR_NAME = 'Tên mới'; GIT_AUTHOR_EMAIL = 'Email mới'" ĐẦU.
Alec the Geek

4
Tại sao nó viết lại tất cả các xác nhận nếu bạn chỉ định HEADở cuối lệnh?
Nick Volynkin

1
Điều này không hoạt động cho kho lưu trữ bitbucket của tôi, ý tưởng nào? Tôi làm git push --force --tags origin 'refs/heads/*'theo lệnh được khuyên
Olorin

1
Lệnh đẩy cho việc này là:$git push --force --tags origin 'refs/heads/master'
HARSH NILESH PATHAK

1
Khéo léo; điều này giữ các dấu thời gian cũ quá.
DharmaTurtle

221

Nó xảy ra khi bạn không khởi tạo $ HOME / .gitconfig. Bạn có thể sửa lỗi này như:

git config --global user.name "you name"
git config --global user.email you@domain.com
git commit --amend --reset-author

thử nghiệm với phiên bản git 1.7.5.4


9
Điều đó hoạt động thực sự tốt trên cam kết cuối cùng. Đẹp và đơn giản. Không được một sự thay đổi toàn cầu, sử dụng --localcông trình quá
Ben

Đây là người chiến thắng lớn đối với tôi! Các git commit --amend --reset-author --no-editlệnh đặc biệt hữu ích nếu bạn đã tạo cam kết với các thông tin tác giả sai, sau đó thiết lập các tác giả chính xác sau-the-thực tế qua git config. Đã lưu một $$ của tôi ngay bây giờ khi tôi phải cập nhật email của mình.
ecbrodie

187

Đối với một cam kết duy nhất:

git commit --amend --author="Author Name <email@address.com>"

(trích từ câu trả lời của Asmeurer)


14
nhưng đó chỉ là khi đó là cam kết gần đây nhất
Richard

4
Theo đó git help commit, git commit --amendthay đổi cam kết tại mũi nhọn của nhánh hiện tại (đó là ĐẦU). Đây thường là cam kết gần đây nhất, nhưng bạn có thể thực hiện bất kỳ cam kết nào bạn muốn bằng cách kiểm tra cam kết đó với git checkout <branch-name>hoặc git checkout <commit-SHA>.
Rory O'Kane

12
Nhưng nếu bạn làm điều đó, tất cả các cam kết đã có cam kết đó với tư cách là cha mẹ sẽ được trỏ đến cam kết sai. Tốt hơn để sử dụng nhánh lọc tại thời điểm đó.
John Gietzen

3
@JohnGietzen: Bạn có thể phản hồi lại các xác nhận đã thay đổi để sửa lỗi đó. Tuy nhiên, nếu bạn đang thực hiện> 1 cam kết, thì như đã đề cập, nhánh lọc có thể sẽ dễ dàng hơn rất nhiều.
Thanatos

5
Lưu ý rằng thay đổi này chỉ cam kết authorchứ không phảicommitter
Nick Volynkin

179

Trong trường hợp chỉ một vài cam kết hàng đầu có tác giả xấu, bạn có thể thực hiện tất cả điều này bên trong git rebase -ibằng cách sử dụng execlệnh và --amendcam kết, như sau:

git rebase -i HEAD~6 # as required

trong đó trình bày cho bạn danh sách các cam kết có thể chỉnh sửa:

pick abcd Someone else's commit
pick defg my bad commit 1
pick 1234 my bad commit 2

Sau đó thêm exec ... --author="..."dòng sau tất cả các dòng với tác giả xấu:

pick abcd Someone else's commit
pick defg my bad commit 1
exec git commit --amend --author="New Author Name <email@address.com>" -C HEAD
pick 1234 my bad commit 2
exec git commit --amend --author="New Author Name <email@address.com>" -C HEAD

lưu và thoát trình soạn thảo (để chạy).

Giải pháp này có thể dài hơn để gõ so với một số giải pháp khác, nhưng nó có thể kiểm soát cao - tôi biết chính xác những gì cam kết nó đạt được.

Cảm ơn @asmeker đã truyền cảm hứng.


26
Chắc chắn là tuyệt vời. Bạn có thể rút ngắn nó bằng cách đặt user.name và user.email trong cấu hình cục bộ của repo không, và sau đó mỗi dòng chỉ là exec git commit --amend --reset-author -C HEAD?
Andrew

1
Câu trả lời chính tắc, để sử dụng nhánh lọc, chỉ cần xóa refs / Heads / master cho tôi. Vì vậy, +1 cho giải pháp có thể kiểm soát, có thể chỉnh sửa của bạn. Cảm ơn!
jmtd

Tại sao bạn bắt đầu với Someone else's committhay vì my bad commit 1? Tôi chỉ cố gắng HEAD^^sửa đổi 2 cam kết cuối cùng, và nó hoạt động hoàn toàn tốt.
fredoverflow

3
Thay vào đó git rebase -i HEAD^^^^^^bạn cũng có thể viếtgit rebase -i HEAD~6
Patrick Schlüter

1
Xin lưu ý rằng điều này thay đổi dấu thời gian của các cam kết. Xem stackoverflow.com/a/11179245/1353267 để quay lại dấu thời gian chính xác
Samveen

111

Github có một giải pháp hay , đó là kịch bản shell sau:

#!/bin/sh

git filter-branch --env-filter '

an="$GIT_AUTHOR_NAME"
am="$GIT_AUTHOR_EMAIL"
cn="$GIT_COMMITTER_NAME"
cm="$GIT_COMMITTER_EMAIL"

if [ "$GIT_COMMITTER_EMAIL" = "your@email.to.match" ]
then
    cn="Your New Committer Name"
    cm="Your New Committer Email"
fi
if [ "$GIT_AUTHOR_EMAIL" = "your@email.to.match" ]
then
    an="Your New Author Name"
    am="Your New Author Email"
fi

export GIT_AUTHOR_NAME="$an"
export GIT_AUTHOR_EMAIL="$am"
export GIT_COMMITTER_NAME="$cn"
export GIT_COMMITTER_EMAIL="$cm"
'

5
Làm việc hoàn hảo. Chỉ cần git reset --hard HEAD^một vài lần trên các kho lưu trữ cục bộ khác để đưa chúng lên phiên bản cũ hơn, - git pulllà phiên bản sửa đổi, và ở đây tôi không có bất kỳ dòng nào chứa unknown <stupid-windows-user@.StupidWindowsDomain.local>(phải yêu thích mặc định của git).
Alan Plum

1
Tôi không thể đẩy sau này. Tôi có phải sử dụng "-f" không?
Giám sát cá

9
Tôi đã làm git push -f. Ngoài ra, repos địa phương phải được reclon sau này.
Giám sát cá

Nếu bạn cần chạy tập lệnh shell trên một nhánh cụ thể, bạn có thể thay đổi dòng cuối cùng thành: "'master..your-Branch-name" (giả sử bạn được phân nhánh của master).
Robert Kajic

Nhấp vào liên kết <giải pháp tốt đẹp> vì tập lệnh đã được cập nhật
gxpr

82

Như docgnome đã đề cập, viết lại lịch sử là nguy hiểm và sẽ phá vỡ kho của người khác.

Nhưng nếu bạn thực sự muốn làm điều đó và bạn đang ở trong môi trường bash (không có vấn đề gì trong Linux, trên Windows, bạn có thể sử dụng git bash, được cung cấp với cài đặt git), hãy sử dụng nhánh lọc git :

git filter-branch --env-filter '
  if [ $GIT_AUTHOR_EMAIL = bad@email ];
    then GIT_AUTHOR_EMAIL=correct@email;
  fi;
export GIT_AUTHOR_EMAIL'

Để tăng tốc mọi thứ, bạn có thể chỉ định một loạt các phiên bản bạn muốn viết lại:

git filter-branch --env-filter '
  if [ $GIT_AUTHOR_EMAIL = bad@email ];
    then GIT_AUTHOR_EMAIL=correct@email;
  fi;
export GIT_AUTHOR_EMAIL' HEAD~20..HEAD

2
Xin lưu ý rằng điều này sẽ để lại bất kỳ thẻ chỉ vào các cam kết cũ. --tag-name-filter catlà tùy chọn "làm cho nó hoạt động".
Roman Starkov

@romkyns có ý kiến ​​gì về cách thay đổi thẻ không?
Nick Volynkin

@NickVolynkin Có, bạn chỉ định --tag-name-filter cat. Đây thực sự nên là hành vi mặc định.
Roman Starkov

48

Khi tiếp quản một cam kết không được hợp nhất từ ​​một tác giả khác, có một cách dễ dàng để xử lý việc này.

git commit --amend --reset-author


1
Đối với một cam kết duy nhất và nếu bạn muốn đặt tên người dùng của mình, đây là cách dễ dàng nhất.
Pedro Benevides

7
Bạn có thể thêm --no-editđể làm cho việc này trở nên dễ dàng hơn, vì nhìn chung hầu hết mọi người sẽ chỉ muốn cập nhật địa chỉ email chứ không phải thông báo cam kết
Plague Hammer

Các bạn có thể vui lòng chia sẻ lệnh git chỉ để cập nhật email / tên người dùng cuối cùng với tên mới
adi

Bạn đã thử điều này? Đó sẽ là một tác dụng phụ của việc này, nếu không stackoverflow.com/a/2717477/654245 trông giống như một con đường tốt.
Ryanmt

47

Bạn có thể sử dụng điều này như một bí danh để bạn có thể làm:

git change-commits GIT_AUTHOR_NAME "old name" "new name"

hoặc trong 10 lần xác nhận gần nhất:

git change-commits GIT_AUTHOR_EMAIL "old@email.com" "new@email.com" HEAD~10..HEAD

Thêm vào ~ / .gitconfig:

[alias]
    change-commits = "!f() { VAR=$1; OLD=$2; NEW=$3; shift 3; git filter-branch --env-filter \"if [[ \\\"$`echo $VAR`\\\" = '$OLD' ]]; then export $VAR='$NEW'; fi\" $@; }; f "

Nguồn: https://github.com/bAFiobo/gitconfig/blob/master/configs/.gitconfig

Hy vọng nó hữu ích.


"git: 'thay đổi-cam kết' không phải là lệnh git. Xem 'git --help'."
Native_Mobile_Arch_Dev

Sau khi lệnh này và đồng bộ hóa với chủ, tất cả các xác nhận trong lịch sử sẽ được sao chép! Ngay cả những người dùng khác :(
Vladimir

@Vladimir dự kiến, vui lòng nghiên cứu về việc thay đổi lịch sử trong git
brauliobo

Đối với tôi, nó dường như chạy trong / bin / sh, vì vậy tôi phải thay thế thử nghiệm dành riêng cho bash bằng thử nghiệm [[ ]]tương thích sh [ ](dấu ngoặc đơn). Bên cạnh đó nó hoạt động rất tốt, cảm ơn!
Steffen Schwigon

39

Đây là phiên bản chi tiết hơn của phiên bản @ Brian:

Để thay đổi tác giả và committer, bạn có thể thực hiện việc này (với các ngắt dòng trong chuỗi có thể có trong bash):

git filter-branch --env-filter '
    if [ "$GIT_COMMITTER_NAME" = "<Old name>" ];
    then
        GIT_COMMITTER_NAME="<New name>";
        GIT_COMMITTER_EMAIL="<New email>";
        GIT_AUTHOR_NAME="<New name>";
        GIT_AUTHOR_EMAIL="<New email>";
    fi' -- --all

Bạn có thể gặp một trong những lỗi sau:

  1. Thư mục tạm thời đã tồn tại
  2. Các tham chiếu bắt đầu bằng ref / gốc đã tồn tại
    (điều này có nghĩa là một nhánh bộ lọc khác đã được chạy trước đó trên kho lưu trữ và tham chiếu nhánh gốc sau đó được sao lưu tại refs / gốc )

Nếu bạn muốn buộc chạy bất chấp các lỗi này, hãy thêm --forcecờ:

git filter-branch --force --env-filter '
    if [ "$GIT_COMMITTER_NAME" = "<Old name>" ];
    then
        GIT_COMMITTER_NAME="<New name>";
        GIT_COMMITTER_EMAIL="<New email>";
        GIT_AUTHOR_NAME="<New name>";
        GIT_AUTHOR_EMAIL="<New email>";
    fi' -- --all

Một lời giải thích nhỏ về -- --alltùy chọn có thể cần thiết: Nó làm cho nhánh bộ lọc hoạt động trên tất cả các phiên bản trên tất cả các ref (bao gồm tất cả các nhánh). Điều này có nghĩa là, ví dụ, các thẻ cũng được viết lại và hiển thị trên các nhánh được viết lại.

Một "lỗi" phổ biến là sử dụng HEADthay thế, có nghĩa là lọc tất cả các sửa đổi trên chỉ nhánh hiện tại . Và sau đó, không có thẻ (hoặc các ref khác) sẽ tồn tại trong nhánh viết lại.


Kudos để cung cấp một thủ tục thay đổi cam kết trên tất cả các ref / chi nhánh.
Johnny Utahh 17/05/2015

25

Một lệnh duy nhất để thay đổi tác giả cho N lần xác nhận cuối cùng:

git rebase -i HEAD~4 -x "git commit --amend --author 'Author Name <author.name@mail.com>' --no-edit"

GHI CHÚ

  • những --no-editlàm cho lá cờ bảo git commit --amendkhông yêu cầu xác nhận thêm
  • Khi bạn sử dụng git rebase -i, bạn có thể chọn thủ công các thay đổi nơi tác giả,

tập tin bạn chỉnh sửa sẽ trông như thế này:

pick 897fe9e simplify code a little
exec git commit --amend --author 'Author Name <author.name@mail.com>' --no-edit
pick abb60f9 add new feature
exec git commit --amend --author 'Author Name <author.name@mail.com>' --no-edit
pick dc18f70 bugfix
exec git commit --amend --author 'Author Name <author.name@mail.com>' --no-edit

Sau đó, bạn vẫn có thể sửa đổi một số dòng để xem nơi bạn muốn thay đổi tác giả. Điều này cung cấp cho bạn một nền tảng tốt đẹp giữa tự động hóa và kiểm soát: bạn thấy các bước sẽ chạy và một khi bạn lưu mọi thứ sẽ được áp dụng ngay lập tức.


Tuyệt vời! Cảm ơn bạn!
Pablo Lalloni

Tôi đã sử dụng HEAD ~ 8 và nó hiển thị nhiều hơn 8 lần xác nhận gần nhất.
Bryan Bryce

1
@BryanBryce nếu có các cam kết hợp nhất có liên quan, mọi thứ trở nên phức tạp :)
Chris Maes

@ChrisMaes Ah, tôi thấy những gì đang xảy ra. Tôi không muốn gây rối với những người đó, chỉ trên chi nhánh tôi đang ở.
Bryan Bryce

Trong trường hợp đó, giả sử bạn phân nhánh từ chủ, bạn có thể:git rebase -i master -x ...
Chris Maes

23
  1. chạy git rebase -i <sha1 or ref of starting point>
  2. đánh dấu tất cả các cam kết mà bạn muốn thay đổi bằng edit(hoặc e)
  3. lặp hai lệnh sau cho đến khi bạn xử lý tất cả các xác nhận:

    git commit --amend --reuse-message=HEAD --author="New Author <new@author.email>" ; git rebase --continue

Điều này sẽ giữ tất cả các thông tin cam kết khác (bao gồm cả ngày). Các --reuse-message=HEADtùy chọn ngăn cản việc biên tập tin nhắn từ phóng.


23

Tôi sử dụng cách sau để viết lại tác giả cho toàn bộ kho lưu trữ, bao gồm các thẻ và tất cả các nhánh:

git filter-branch --tag-name-filter cat --env-filter "
  export GIT_AUTHOR_NAME='New name';
  export GIT_AUTHOR_EMAIL='New email'
" -- --all

Sau đó, như được mô tả trong trang MAN của nhánh bộ lọc , hãy xóa tất cả các giới thiệu gốc được sao lưu bởi filter-branch(đây là phá hủy, sao lưu trước):

git for-each-ref --format="%(refname)" refs/original/ | \
xargs -n 1 git update-ref -d

2
Nó rất quan trọng để sử dụng --tag-name-filter cat. Nếu không, thẻ của bạn sẽ vẫn còn trên chuỗi cam kết ban đầu. Các câu trả lời khác không đề cập đến điều này.
jeberle

21

Tôi đã điều chỉnh giải pháp này hoạt động bằng cách nhập một đơn giản author-conv-file(định dạng giống như với git-cvsimport ). Nó hoạt động bằng cách thay đổi tất cả người dùng như được xác định trong author-conv-filetất cả các chi nhánh.

Chúng tôi đã sử dụng kết hợp với cvs2gitđể di chuyển kho lưu trữ của chúng tôi từ cvs sang git.

tức là mẫu author-conv-file

john=John Doe <john.doe@hotmail.com>
jill=Jill Doe <jill.doe@hotmail.com>

Kịch bản:

 #!/bin/bash

 export $authors_file=author-conv-file

 git filter-branch -f --env-filter '

 get_name () {
     grep "^$1=" "$authors_file" |
     sed "s/^.*=\(.*\) <.*>$/\1/"
 }

 get_email () {
     grep "^$1=" "$authors_file" |
     sed "s/^.*=.* <\(.*\)>$/\1/"
 }

 GIT_AUTHOR_NAME=$(get_name $GIT_COMMITTER_NAME) &&
     GIT_AUTHOR_EMAIL=$(get_email $GIT_COMMITTER_NAME) &&
     GIT_COMMITTER_NAME=$GIT_AUTHOR_NAME &&
     GIT_COMMITTER_EMAIL=$GIT_AUTHOR_EMAIL &&
     export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL &&
     export GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL
 ' -- --all

Cảm ơn, tôi tự hỏi tại sao đây không phải là chức năng cốt lõi của git (hoặc git-svn). Điều này có thể được thực hiện với một cờ cho bản sao git svn, nhưng không phải trong nhánh bộ lọc git ...
Daniel Hershcovich

20

Tôi nên chỉ ra rằng nếu vấn đề duy nhất là tác giả / email khác với thông thường của bạn, thì đây không phải là vấn đề. Cách khắc phục chính xác là tạo một tệp được gọi .mailmapở gốc thư mục với các dòng như

Name you want <email you want> Name you don't want <email you don't want>

Và từ đó trở đi, các lệnh như git shortlogsẽ coi hai tên đó giống nhau (trừ khi bạn đặc biệt nói với chúng là không). Xem http://schacon.github.com/git/git-shortlog.html để biết thêm thông tin.

Điều này có lợi thế của tất cả các giải pháp khác ở đây là bạn không phải viết lại lịch sử, điều này có thể gây ra vấn đề nếu bạn có một dòng ngược và luôn là một cách tốt để vô tình làm mất dữ liệu.

Tất nhiên, nếu bạn đã cam kết một cái gì đó là chính mình và nó thực sự phải là một người khác và bạn không ngại viết lại lịch sử vào thời điểm này, thay đổi tác giả cam kết có lẽ là một ý tưởng tốt cho mục đích ghi công (trong trường hợp tôi hướng bạn đến câu trả lời khác ở đây).


18

Tôi thấy các phiên bản được trình bày theo cách hung hăng, đặc biệt nếu bạn cam kết các bản vá từ các nhà phát triển khác, điều này về cơ bản sẽ đánh cắp mã của họ.

Phiên bản dưới đây không hoạt động trên tất cả các nhánh và thay đổi riêng tác giả và người viết để ngăn chặn điều đó.

Kudos để leif81 cho tất cả các tùy chọn.

#!/bin/bash

git filter-branch --env-filter '
if [ "$GIT_AUTHOR_NAME" = "<old author>" ];
then
    GIT_AUTHOR_NAME="<new author>";
    GIT_AUTHOR_EMAIL="<youmail@somehost.ext>";
fi
if [ "$GIT_COMMITTER_NAME" = "<old committer>" ];
then
    GIT_COMMITTER_NAME="<new commiter>";
    GIT_COMMITTER_EMAIL="<youmail@somehost.ext>";
fi
' -- --all

18
  1. Thay đổi cam kết author name & emailbằng Amend, sau đó thay thế old-commit with new-one:

    $ git checkout <commit-hash>                            # checkout to the commit need to modify  
    $ git commit --amend --author "name <author@email.com>" # change the author name and email
    
    $ git replace <old-commit-hash> <new-commit-hash>      # replace the old commit by new one
    $ git filter-branch -- --all                           # rewrite all futures commits based on the replacement                   
    
    $ git replace -d <old-commit-hash>     # remove the replacement for cleanliness 
    $ git push -f origin HEAD              # force push 
    
  2. Một cách khác Rebasing:

    $ git rebase -i <good-commit-hash>      # back to last good commit
    
    # Editor would open, replace 'pick' with 'edit' before the commit want to change author
    
    $ git commit --amend --author="author name <author@email.com>"  # change the author name & email
    
    # Save changes and exit the editor
    
    $ git rebase --continue                # finish the rebase
    

2
Câu trả lời rất hay. Tôi thích rằng các thay đổi được gói gọn từ chính bản cập nhật cho đến việc dọn sạch các cam kết git
Aleks

12

Cách nhanh nhất, dễ nhất để làm điều này là sử dụng đối số --exec của git rebase:

git rebase -i -p --exec 'git commit --amend --reset-author --no-edit'

Điều này sẽ tạo ra một danh sách việc cần làm như thế này:

pick ef11092 Blah blah blah
exec git commit --amend --reset-author --no-edit
pick 52d6391 Blah bloh bloo
exec git commit --amend --reset-author --no-edit
pick 30ebbfe Blah bluh bleh
exec git commit --amend --reset-author --no-edit
...

và điều này sẽ hoạt động tự động, hoạt động khi bạn có hàng trăm lần xác nhận.


9

Nếu bạn là người dùng duy nhất của kho lưu trữ này, bạn có thể viết lại lịch sử bằng cách sử dụng git filter-branch(như Svick đã viết ) hoặc git fast-export/ git fast-importplus script script (như được mô tả trong bài viết được tham chiếu trong câu trả lời docgnome ) hoặc rebase tương tác . Nhưng một trong hai sẽ thay đổi sửa đổi từ lần cam kết thay đổi đầu tiên trở đi; điều này có nghĩa là rắc rối cho bất cứ ai dựa trên những thay đổi của anh ấy / cô ấy trên chi nhánh của bạn trước khi viết lại.

HỒI PHỤC

Nếu các nhà phát triển khác không dựa trên công việc của họ trên phiên bản viết lại, giải pháp đơn giản nhất sẽ là sao chép lại (sao chép lại).

Ngoài ra, họ có thể thử git rebase --pull, sẽ chuyển tiếp nhanh nếu không có bất kỳ thay đổi nào trong kho lưu trữ của họ hoặc khởi động lại chi nhánh của họ trên đầu trang của các cam kết được viết lại (chúng tôi muốn tránh hợp nhất, vì nó sẽ tiếp tục viết lại trước đó). Tất cả điều này giả định rằng họ không làm việc được; sử dụng git stashđể bỏ đi những thay đổi khác.

Nếu nhà phát triển khác sử dụng các ngành chức năng, và / hoặc git pull --rebasekhông hoạt động ví dụ như vì thượng nguồn không được thiết lập, họ phải rebase công việc của họ trên đầu trang của các cam kết sau khi viết lại. Ví dụ: ngay sau khi tìm nạp các thay đổi mới ( git fetch), đối với một masternhánh dựa trên / rẽ nhánh từ origin/master, người ta cần chạy

$ git rebase --onto origin/master origin/master@{1} master

Đây origin/master@{1}là trạng thái viết lại trước (trước khi tìm nạp), xem phần gitrevutions .


Giải pháp thay thế sẽ là sử dụng refs / thay thế / cơ chế, có sẵn trong Git kể từ phiên bản 1.6.5. Trong giải pháp này, bạn cung cấp thay thế cho các cam kết có email sai; sau đó, bất kỳ ai tìm nạp 'ref' refs (một cái gì đó giống như fetch = +refs/replace/*:refs/replace/*refspec ở vị trí thích hợp trong họ .git/config ) sẽ được thay thế một cách minh bạch, và những người không lấy các ref đó sẽ thấy các cam kết cũ.

Thủ tục diễn ra như thế này:

  1. Tìm tất cả các xác nhận với email sai, ví dụ như sử dụng

    $ git log --author=user@wrong.email --all
    
  2. Đối với mỗi cam kết sai, hãy tạo một cam kết thay thế và thêm nó vào cơ sở dữ liệu đối tượng

    $ git cat-file -p <ID of wrong commit> | 
      sed -e 's/user@wrong\.email/user@example.com/g' > tmp.txt
    $ git hash-object -t commit -w tmp.txt
    <ID of corrected commit>
    
  3. Bây giờ bạn đã sửa lỗi cam kết trong cơ sở dữ liệu đối tượng, bạn phải yêu cầu git tự động thay thế và minh bạch thay thế cam kết sai bằng cách sửa lỗi bằng git replacelệnh:

    $ git replace <ID of wrong commit> <ID of corrected commit>
    
  4. Cuối cùng, liệt kê tất cả các thay thế để kiểm tra xem thủ tục này có bị hủy bỏ không

    $ git replace -l
    

    và kiểm tra nếu thay thế diễn ra

    $ git log --author=user@wrong.email --all
    

Tất nhiên, bạn có thể tự động hóa quy trình này ... tốt, tất cả ngoại trừ việc sử dụng git replacechế độ hàng loạt chưa có (chưa), vì vậy bạn sẽ phải sử dụng vòng lặp shell cho điều đó hoặc thay thế "bằng tay".

KHÔNG KIỂM TRA! YMMV.

Lưu ý rằng bạn có thể gặp một số góc khó khăn khi sử dụng refs/replace/cơ chế: nó mới và chưa được thử nghiệm tốt .


6

Nếu các xác nhận bạn muốn sửa là những cam kết mới nhất và chỉ một vài trong số chúng, bạn có thể sử dụng kết hợp git resetgit stashquay lại cam kết chúng một lần nữa sau khi định cấu hình đúng tên và email.

Trình tự sẽ giống như thế này (đối với 2 lần xác nhận sai, không có thay đổi đang chờ xử lý):

git config user.name <good name>
git config user.email <good email>
git reset HEAD^
git stash
git reset HEAD^
git commit -a
git stash pop
git commit -a

5

Nếu bạn đang sử dụng Eclipse với EGit, thì có một giải pháp khá dễ dàng.
Giả định: bạn có các cam kết trong một nhánh cục bộ 'local_master_user_x' không thể được đẩy đến một nhánh 'chủ' từ xa vì người dùng không hợp lệ.

  1. Kiểm tra chi nhánh từ xa 'chủ'
  2. Chọn dự án / thư mục / tệp mà 'local_master_user_x' chứa các thay đổi
  3. Nhấp chuột phải - Thay thế bằng - Chi nhánh - 'local_master_user_x'
  4. Cam kết những thay đổi này một lần nữa, lần này là người dùng chính xác và vào 'chủ' chi nhánh địa phương
  5. Đẩy đến 'chủ' từ xa

5

Sử dụng rebase tương tác, bạn có thể đặt lệnh sửa đổi sau mỗi cam kết bạn muốn thay đổi. Ví dụ:

pick a07cb86 Project tile template with full details and styling
x git commit --amend --reset-author -Chead

3
Vấn đề với điều này là siêu dữ liệu cam kết khác (ví dụ: ngày và giờ) cũng được sửa đổi. Tôi chỉ thấy rằng ra một cách khó khăn ;-).
halfer

5

Lưu ý rằng các cửa hàng git hai địa chỉ e-mail khác nhau, một cho committer (người cam kết thay đổi) và một số khác cho tác giả (người viết thay đổi).

Thông tin committer không được hiển thị ở hầu hết các nơi, nhưng bạn có thể thấy nó với git log -1 --format=%cn,%ce(hoặc sử dụng showthay vì logchỉ định một cam kết cụ thể).

Mặc dù việc thay đổi tác giả của lần xác nhận cuối cùng của bạn cũng đơn giản như vậy git commit --amend --author "Author Name <email@example.com>", không có đối số hoặc đối số nào thực hiện tương tự với thông tin của người gửi.

Giải pháp là (tạm thời hoặc không) thay đổi thông tin người dùng của bạn, sau đó sửa đổi cam kết, sẽ cập nhật thông tin đến thông tin hiện tại của bạn:

git config user.email my_other_email@example.com 
git commit --amend

Lưu ý rằng giá trị cũ vẫn còn ở một vài nơi path\to\repo\.git. Tôi không chắc chắn những gì bạn cần làm để hoàn thành nó. Không may sửa đổi (?) Dường như không xóa.
ruffin

5

Chúng tôi đã gặp sự cố ngày hôm nay khi một nhân vật UTF8 trong tên tác giả đã gây rắc rối trên máy chủ xây dựng, vì vậy chúng tôi phải viết lại lịch sử để sửa lỗi này. Các bước thực hiện là:

Bước 1: Thay đổi tên người dùng của bạn trong git cho tất cả các cam kết trong tương lai, theo hướng dẫn tại đây: https://help.github.com/articles/setting-your-username-in-git/

Bước 2: Chạy tập lệnh bash sau:

#!/bin/sh

REPO_URL=ssh://path/to/your.git
REPO_DIR=rewrite.tmp

# Clone the repository
git clone ${REPO_URL} ${REPO_DIR}

# Change to the cloned repository
cd ${REPO_DIR}

# Checkout all the remote branches as local tracking branches
git branch --list -r origin/* | cut -c10- | xargs -n1 git checkout

# Rewrite the history, use a system that will preseve the eol (or lack of in commit messages) - preferably Linux not OSX
git filter-branch --env-filter '
OLD_EMAIL="me@something.com"
CORRECT_NAME="New Me"

if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_COMMITTER_NAME="$CORRECT_NAME"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_AUTHOR_NAME="$CORRECT_NAME"
fi
' --tag-name-filter cat -- --branches --tags

# Force push the rewritten branches + tags to the remote
git push -f

# Remove all knowledge that we did something
rm -rf ${REPO_DIR}

# Tell your colleagues to `git pull --rebase` on all their local remote tracking branches

Tổng quan nhanh: Kiểm tra kho lưu trữ của bạn vào tệp tạm thời, kiểm tra tất cả các nhánh từ xa, chạy tập lệnh sẽ viết lại lịch sử, đẩy mạnh trạng thái mới và báo cho tất cả các đồng nghiệp của bạn thực hiện thao tác kéo lại để có các thay đổi.

Chúng tôi đã gặp sự cố với việc chạy này trên OS X vì nó bằng cách nào đó đã làm rối tung các kết thúc dòng trong các thông báo cam kết, vì vậy chúng tôi phải chạy lại nó trên máy Linux sau đó.


5

Vấn đề của bạn là thực sự phổ biến. Xem " Sử dụng sơ đồ thư để sửa danh sách tác giả trong Git "

Để đơn giản, tôi đã tạo ra một kịch bản để giảm bớt quá trình: git-changemail

Sau khi đặt tập lệnh đó trên đường dẫn của bạn, bạn có thể đưa ra các lệnh như:

  • Thay đổi kết quả phù hợp với tác giả trên chi nhánh hiện tại

    $ git changemail -a old@email.com -n newname -m new@email.com
    
  • Thay đổi kết quả khớp tác giả và committer trên <nhánh> và <nhánh2>. Chuyển -fđến nhánh bộ lọc để cho phép viết lại bản sao lưu

    $ git changemail -b old@email.com -n newname -m new@email.com -- -f &lt;branch> &lt;branch2>
    
  • Hiển thị người dùng hiện tại trên repo

    $ git changemail --show-both
    

Nhân tiện, sau khi thực hiện các thay đổi của bạn, hãy dọn sạch bản sao lưu khỏi nhánh bộ lọc bằng: git-backup-clean


1
Khi tôi chạy lệnh của bạn, nó báo "gây tử vong: không thể thực thi 'git-changemail': Quyền bị từ chối"
Govind


3

Tôi muốn thêm ví dụ của tôi quá. Tôi muốn tạo một bash_function với tham số đã cho.

cái này hoạt động trong mint-linux-17.3

# $1 => email to change, $2 => new_name, $3 => new E-Mail

function git_change_user_config_for_commit {

 # defaults
 WRONG_EMAIL=${1:-"you_wrong_mail@hello.world"}
 NEW_NAME=${2:-"your name"}
 NEW_EMAIL=${3:-"new_mail@hello.world"}

 git filter-branch -f --env-filter "
  if [ \$GIT_COMMITTER_EMAIL = '$WRONG_EMAIL' ]; then
    export GIT_COMMITTER_NAME='$NEW_NAME'
    export GIT_COMMITTER_EMAIL='$NEW_EMAIL'
  fi
  if [ \$GIT_AUTHOR_EMAIL = '$WRONG_EMAIL' ]; then
    export GIT_AUTHOR_NAME='$NEW_NAME'
    export GIT_AUTHOR_EMAIL='$NEW_EMAIL'
  fi
 " --tag-name-filter cat -- --branches --tags;
}

2

Nếu bạn là người dùng duy nhất của repo này hoặc bạn không quan tâm đến việc có thể phá vỡ repo cho những người dùng khác, thì có. Nếu bạn đã đẩy những cam kết này và chúng tồn tại ở nơi nào đó có thể truy cập chúng, thì không, trừ khi bạn không quan tâm đến việc phá vỡ các repos của người khác. Vấn đề là bằng cách thay đổi các cam kết này, bạn sẽ tạo ra các SHA mới, điều này sẽ khiến chúng được coi là các cam kết khác nhau. Khi một người khác cố gắng kéo theo những cam kết thay đổi này, lịch sử sẽ khác đi và kaboom.

Trang này http://inputvalidation.blogspot.com/2008/08/how-to-change-git-commit- mượt.html mô tả cách thực hiện. (Tôi chưa thử điều này nên YMMV)


Vì vậy, không có cách nào an toàn để viết lại user.email. Mà không nổ tung mọi người khác. Tôi biết rằng viết lại lịch sử là một ý tưởng tồi, tôi chỉ nghĩ rằng có thể có một cách sạch sẽ để làm điều đó một cách an toàn. Cảm ơn.
manumoomoo

@mediaslave: Thử refs/replace/cơ chế.
Jakub Narębski

meta.stackexchange.com/a/8259/184684 - hay còn gọi là liên kết tổng hợp để biến chúng thành câu trả lời.
ruffin
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.