Có thể di chuyển / đổi tên các tệp trong Git và duy trì lịch sử của chúng không?


666

Tôi muốn đổi tên / di chuyển một cây con dự án trong Git di chuyển nó từ

/project/xyz

đến

/components/xyz

Nếu tôi sử dụng một đồng bằng git mv project components, thì tất cả lịch sử cam kết cho xyz projectbị mất. Có cách nào để di chuyển cái này sao cho lịch sử được duy trì?



2
Tôi chỉ muốn lưu ý rằng tôi vừa kiểm tra các tệp di chuyển qua hệ thống tệp và sau khi cam kết (thông qua intellij) tôi có thể xem toàn bộ lịch sử (bao gồm cả lịch sử khi nó ở một vị trí khác) khi xem lịch sử (một lần nữa trong intellij). Tôi cho rằng intellij không làm gì đặc biệt để làm điều đó, vì vậy thật tuyệt khi biết rằng ít nhất lịch sử có thể được truy tìm.
BT

Để biết các quy tắc được theo sau bởi Git khi phát hiện đổi tên thư mục, hãy xem câu trả lời của tôi dưới đây
VonC

Tôi đã viết một câu trả lời ở đây. Tôi hy vọng nó hoạt động. stackoverflow.com/questions/10828267/ cường
Mahmut EFE

Câu trả lời:


650

Git phát hiện đổi tên thay vì duy trì hoạt động với cam kết, do đó, dù bạn sử dụng git mvhay mvkhông quan trọng.

Các loglệnh có một --followlập luận cho rằng tiếp tục lịch sử trước một hoạt động đổi tên, ví dụ, nó tìm kiếm nội dung tương tự bằng cách sử dụng công nghệ tự động:

http://git-scm.com/docs/git-log

Để tra cứu toàn bộ lịch sử, sử dụng lệnh sau:

git log --follow ./path/to/file

63
Tôi nghi ngờ đây là một xem xét hiệu suất. Nếu bạn không cần lịch sử đầy đủ, chắc chắn sẽ mất nhiều thời gian hơn để quét nội dung. Cách dễ nhất là thiết lập bí danh git config alias.logf "log --follow"và chỉ cần viết git logf ./path/to/file.
Quân đội Thomsen

13
@TroelsThomsen e-mail này bởi Linus Torvalds, liên kết từ các câu trả lời này , chỉ ra rằng đó là một sự lựa chọn thiết kế có chủ ý của Git vì nó bị cáo buộc mạnh hơn theo dõi đặt lại tên, vv
Emil Lundberg

127
Câu trả lời này là một chút sai lệch. Git không "phát hiện đổi tên", nhưng rất muộn trong trò chơi; câu hỏi là hỏi làm thế nào bạn đảm bảo Git theo dõi đổi tên và ai đó đọc nó có thể dễ dàng suy ra rằng Git tự động phát hiện chúng cho bạn và ghi chú về nó. Nó không. Git không có xử lý thực sự cho việc đổi tên, và thay vào đó, có các công cụ hợp nhất / nhật ký cố gắng tìm hiểu điều gì đã xảy ra - và hiếm khi làm cho đúng. Linus có một lập luận sai lầm nhưng kịch liệt là tại sao git không bao giờ nên làm điều đó đúng cách và theo dõi đổi tên một cách rõ ràng. Vì vậy, chúng tôi bị mắc kẹt ở đây.
Chris Moschini

29
Quan trọng: nếu bạn đổi tên thư mục, ví dụ trong khi đổi tên gói Java, hãy đảm bảo thực hiện hai lần xác nhận, trước tiên là lệnh 'git mv {old} {new}', thứ hai cho các cập nhật của tất cả các tệp Java tham chiếu đến thay đổi thư mục gói. Nếu không, git không thể theo dõi các tệp riêng lẻ ngay cả với tham số --follow.
nn4l

44
Mặc dù Linus có thể mắc rất ít lỗi, nhưng điều này dường như là một. Chỉ cần đổi tên thư mục sẽ khiến một delta lớn được tải lên GitHub. Điều này khiến tôi thận trọng về việc đổi tên các thư mục của mình ... nhưng đó là một chiếc áo khoác thẳng khá lớn cho một lập trình viên. Thỉnh thoảng, tôi PHẢI xác định lại ý nghĩa của một cái gì đó, hoặc thay đổi cách mọi thứ được phân loại. Linus: "Nói cách khác, tôi đúng. Tôi luôn luôn đúng, nhưng đôi khi tôi đúng hơn những lần khác. Và chết tiệt, khi tôi nói 'tập tin không quan trọng', tôi thực sự đúng ( tm). " ... Tôi có nghi ngờ về điều đó.
Gabe Halsmer

93

thể đổi tên một tệp và giữ nguyên lịch sử, mặc dù nó khiến tệp bị đổi tên trong toàn bộ lịch sử của kho lưu trữ. Điều này có lẽ chỉ dành cho những người yêu thích git-log-ám ảnh và có một số hàm ý nghiêm trọng, bao gồm:

  • Bạn có thể viết lại một lịch sử được chia sẻ, đó là điều KHÔNG quan trọng nhất khi sử dụng Git. Nếu ai đó đã nhân bản kho lưu trữ, bạn sẽ phá vỡ nó. Họ sẽ phải nhân bản lại để tránh đau đầu. Điều này có thể ổn nếu việc đổi tên đủ quan trọng, nhưng bạn sẽ cần cân nhắc kỹ điều này - cuối cùng bạn có thể làm đảo lộn toàn bộ cộng đồng mã nguồn mở!
  • Nếu bạn đã tham chiếu tệp bằng tên cũ trước đó trong lịch sử kho lưu trữ, thì bạn thực sự phá vỡ các phiên bản trước đó. Để khắc phục điều này, bạn sẽ phải nhảy thêm một chút. Nó không phải là không thể, chỉ tẻ nhạt và có thể không đáng.

Bây giờ, vì bạn vẫn ở với tôi, bạn có thể là nhà phát triển solo đổi tên một tệp hoàn toàn bị cô lập. Hãy di chuyển một tập tin bằng cách sử dụng filter-tree!

Giả sử bạn sẽ di chuyển một tệp oldvào một thư mục dirvà đặt tên cho nónew

Điều này có thể được thực hiện với git mv old dir/new && git add -u dir/new, nhưng điều đó phá vỡ lịch sử.

Thay thế:

git filter-branch --tree-filter 'if [ -f old ]; then mkdir dir && mv old dir/new; fi' HEAD

sẽ làm lại mọi cam kết trong nhánh, thực hiện lệnh trong tích tắc cho mỗi lần lặp. Rất nhiều thứ có thể sai khi bạn làm điều này. Tôi thường kiểm tra xem tập tin có hiện diện không (nếu không nó vẫn chưa di chuyển) và sau đó thực hiện các bước cần thiết để đánh bóng cây theo ý thích của tôi. Ở đây bạn có thể sed qua các tập tin để thay đổi các tham chiếu đến tập tin, v.v. Tự rút khỏi! :)

Khi hoàn thành, tập tin được di chuyển và nhật ký còn nguyên vẹn. Bạn cảm thấy như một tên cướp biển ninja.

Cũng thế; Tất nhiên, thư mục mkdir chỉ cần thiết nếu bạn di chuyển tệp sang một thư mục mới. Các nếu sẽ tránh được việc tạo ra các thư mục này trước đó trong lịch sử hơn tập tin của bạn tồn tại.


57
Là một người yêu thích git-log-ám ảnh, tôi sẽ không làm điều này. Các tập tin không được đặt tên là tại những thời điểm đó, do đó lịch sử phản ánh một tình huống không bao giờ tồn tại. Ai biết bài kiểm tra nào có thể phá vỡ trong quá khứ! Nguy cơ phá vỡ các phiên bản trước đó là khá nhiều trường hợp không đáng.
Vincent

7
@Vincent Bạn hoàn toàn đúng, và tôi đã cố gắng nói rõ nhất có thể về sự không phù hợp của giải pháp này là phù hợp. Tôi cũng nghĩ rằng chúng ta đang nói về hai ý nghĩa của từ "lịch sử" trong trường hợp này, tôi đánh giá cao cả hai.
Øystein Steimler

6
Tôi thấy có những tình huống mà người ta có thể cần điều này. Nói rằng tôi đã phát triển một cái gì đó trong nhánh cá nhân của riêng tôi, mà bây giờ tôi muốn hợp nhất ngược dòng. Nhưng tôi phát hiện ra, tên tệp không phù hợp, vì vậy tôi thay đổi nó cho toàn bộ chi nhánh cá nhân của mình. Bằng cách đó tôi có thể giữ một lịch sử đúng đắn và có tên chính xác ngay từ đầu.
dùng2291758

3
@ user2291758 đó là trường hợp sử dụng của tôi. Các lệnh git mạnh hơn này rất nguy hiểm nhưng điều đó không có nghĩa là chúng không có các trường hợp sử dụng rất hấp dẫn nếu bạn biết bạn đang làm gì!
philix

1
nếu có thể, sử dụng --index-filterđổi tên sẽ nhanh hơn nhiều vì cây sẽ không phải được kiểm tra và đăng nhập lại trên mỗi cam kết. --index-filterhành động trực tiếp trên mỗi chỉ số cam kết.
Thomas Guyot-Sionnest

87

Không.

Câu trả lời ngắn gọn là KHÔNG . Không thể đổi tên một tệp trong Git và ghi nhớ lịch sử. Và đó là một nỗi đau.

Tin đồn có nó git log --follow--find-copies-hardersẽ hoạt động, nhưng nó không hoạt động đối với tôi, ngay cả khi không có thay đổi nào đối với nội dung tệp và các động thái đã được thực hiện git mv.

(Ban đầu tôi đã sử dụng Eclipse để đổi tên và cập nhật các gói trong một hoạt động, trong đó có thể đã nhầm lẫn Git. Nhưng đó là một điều rất phổ biến để làm. --followDường như để làm việc nếu chỉ có một mvđược thực hiện và sau đó một commitmvkhông phải là quá xa.)

Linus nói rằng bạn phải hiểu toàn bộ nội dung của một dự án phần mềm, không cần phải theo dõi các tệp riêng lẻ. Đáng buồn thay, bộ não nhỏ của tôi không thể làm điều đó.

Điều thực sự khó chịu là rất nhiều người đã vô thức lặp lại tuyên bố rằng Git tự động theo dõi các chuyển động. Họ đã lãng phí thời gian của tôi. Git không có điều đó. Theo thiết kế (!) Git hoàn toàn không theo dõi di chuyển.

Giải pháp của tôi là đổi tên các tập tin trở lại vị trí ban đầu của chúng. Thay đổi phần mềm để phù hợp với kiểm soát nguồn. Với Git, bạn dường như chỉ cần "git" nó ngay lần đầu tiên.

Thật không may, điều đó phá vỡ Eclipse, dường như sử dụng --follow. git log --followđôi khi không hiển thị toàn bộ lịch sử của các tệp có lịch sử đổi tên phức tạp mặc dù git logcó. (Tôi không biết tại sao.)

(Có một số hack quá thông minh quay trở lại và đề xuất công việc cũ, nhưng chúng khá đáng sợ. Xem GitHub-Gist: emiller / git-mv-with-history .)


2
Tôi tin bạn đúng. Tôi chỉ cố gắng sử dụng php-cs-fixer để định dạng lại nguồn cho dự án Laravel 5 của mình nhưng nó khăng khăng thay đổi cách viết hoa của mệnh đề không gian tên để khớp với giá trị chữ thường của thư mục ứng dụng. Nhưng không gian tên (hoặc tự động tải của nhà soạn nhạc) chỉ hoạt động với CamelCase. Tôi cần thay đổi cách viết hoa của thư mục thành Ứng dụng nhưng điều này khiến những thay đổi của tôi bị mất. Đây là ví dụ tầm thường nhất, nhưng cho thấy git heuristic không thể theo dõi ngay cả những thay đổi tên đơn giản nhất (--follow và --find-copy-hard nên là quy tắc, không phải là ngoại lệ).
Zack Morris

6
git -1, lật đổ +1
Cosmin

Điều này có còn đúng không? Đó là lý do nhiều hơn để tôi ở lại với tfs bây giờ, giữ cho lịch sử của một tệp được di chuyển / đổi tên là điều bắt buộc trong một dự án lớn.
Cesar

@Cesar Nếu bằng cách "nhớ lịch sử", anh ta có nghĩa là "theo dõi tên khi xem nhật ký" (đó là điều hữu ích duy nhất chúng ta nên quan tâm), thì điều này không bao giờ đúng! Git không "ghi lại" tên, nhưng các công cụ có thể dễ dàng phát hiện ra chúng và hiển thị cho chúng ta tên và di chuyển. Nếu "nó không hoạt động" cho ai đó, anh ấy / cô ấy nên thay đổi các công cụ mà anh ấy / cô ấy đang sử dụng. Có rất nhiều GUI Git tuyệt vời có khả năng này.
Mohammad Deh Afghanistan

Câu trả lời ngắn gọn là có. Phiên bản hiện tại của Git cũng hỗ trợ "git log --follow". và tôi đồng ý với @MohammadDeh Afghanistan
vào

42
git log --follow [file]

sẽ cho bạn thấy lịch sử thông qua đổi tên.


29
Có vẻ như điều này đòi hỏi bạn phải cam kết chỉ đổi tên trước khi bạn bắt đầu sửa đổi tệp. Nếu bạn di chuyển tệp (trong vỏ) và sau đó thay đổi tệp, tất cả các cược đã tắt.
yoyo

22
@yoyo: đó là vì git không theo dõi đổi tên, nó phát hiện ra chúng. A git mvvề cơ bản làm a git rm && git add. Có các tùy chọn như -M90/ --find-renames=90để xem xét một tệp được đổi tên khi nó giống nhau 90%.
vdboor

22

Tôi làm:

git mv {old} {new}
git add -u {new}

3
-U dường như không làm gì cho tôi, có phải là để cập nhật lịch sử không?
jeremy

1
Có lẽ bạn muốn hành vi -Athay thế? Một lần nữa, xem tại đây: git-scm.com/docs/git-add
James M. Greene

1
Nó không thêm các tệp, tuy nhiên nó không cập nhật lịch sử để 'tên tệp nhật ký git' hiển thị toàn bộ lịch sử. Nó chỉ hiển thị toàn bộ lịch sử nếu bạn vẫn sử dụng tùy chọn --follow.
ngốc

3
Tôi đã thực hiện một bộ tái cấu trúc phức tạp để di chuyển một thư mục bao gồm (sử dụng mv, không phải git mv) và sau đó thay đổi rất nhiều đường dẫn #incoide trong các tệp được đổi tên. git không thể tìm thấy đủ sự tương đồng để theo dõi lịch sử. Nhưng git add -u chỉ là thứ tôi cần. trạng thái git hiện chỉ ra "đã đổi tên" trong đó trước khi nó hiển thị "đã xóa" và "tệp mới".
AndyJost

1
Có rất nhiều câu hỏi về SO giải quyết mục đích của git add -u. Các tài liệu Git có xu hướng không có ích, và là nơi cuối cùng tôi muốn tìm. Đây là một bài viết hiển thị git add -utrong hành động: stackoverflow.com/a/2117202 .
tộc

17

Tôi muốn đổi tên / di chuyển một cây con dự án trong Git di chuyển nó từ

/project/xyz

đến

/ thành phần / xyz

Nếu tôi sử dụng một đồng bằng git mv project components, thì tất cả lịch sử cam kết cho xyzdự án sẽ bị mất.

Không (8 năm sau, Git 2.19, Q3 2018), bởi vì Git sẽ phát hiện đổi tên thư mục , và điều này bây giờ được ghi lại tốt hơn.

Xem cam kết b00bf1c , cam 1.634.688 , cam kết 0661e49 , cam kết 4d34dff , cam kết 983f464 , cam kết c840e1a , cam 9.929.430 (27 tháng 6 năm 2018), và cam kết d4e8062 , cam kết 5dacd4a (Tháng Sáu 25, 2018) bởi Elijah Newren ( newren) .
(Được hợp nhất bởi Junio ​​C Hamano - gitster- trong cam kết 0ce5a69 , ngày 24 tháng 7 năm 2018)

Điều đó hiện được giải thích trong Documentation/technical/directory-rename-detection.txt:

Thí dụ:

Khi tất cả x/a, x/bx/cđã chuyển đến z/a, z/bz/c, có khả năng x/dđược thêm vào trong thời gian đó cũng muốn chuyển đến z/dbằng cách lấy gợi ý rằng toàn bộ thư mục ' x' đã chuyển đến ' z'.

Nhưng chúng là nhiều trường hợp khác, như:

một mặt của lịch sử đổi tên x -> zvà mặt kia đổi tên một số tệp thành x/e, khiến cho sự hợp nhất phải thực hiện đổi tên bắc cầu.

Để đơn giản hóa việc phát hiện đổi tên thư mục, các quy tắc đó được thi hành bởi Git:

một vài quy tắc cơ bản giới hạn khi phát hiện đổi tên thư mục:

  1. Nếu một thư mục nhất định vẫn tồn tại trên cả hai mặt của hợp nhất, chúng tôi không coi nó đã được đổi tên.
  2. Nếu một tập hợp con của các tệp được đổi tên có một tệp hoặc thư mục theo cách (hoặc sẽ theo cách của nhau), hãy "tắt" thư mục đổi tên cho các đường dẫn phụ cụ thể đó và báo cáo xung đột cho người dùng .
  3. Nếu phía bên kia của lịch sử đã đổi tên thư mục thành đường dẫn mà phía lịch sử của bạn đã đổi tên, thì hãy bỏ qua việc đổi tên cụ thể đó từ phía bên kia của lịch sử đối với bất kỳ đổi tên thư mục ngầm nào (nhưng cảnh báo người dùng).

Bạn có thể thấy rất nhiều bài kiểm tra t/t6043-merge-rename-directories.sh, trong đó cũng chỉ ra rằng:

  • a) Nếu đổi tên chia một thư mục thành hai hoặc nhiều thư mục khác, thư mục có nhiều tên đổi nhất, "thắng".
  • b) Tránh phát hiện đổi tên thư mục cho một đường dẫn, nếu đường dẫn đó là nguồn của việc đổi tên ở hai bên của hợp nhất.
  • c) Chỉ áp dụng đổi tên thư mục ngầm cho các thư mục nếu phía bên kia của lịch sử là người thực hiện đổi tên.

15

Mục tiêu

  • Sử dụng (lấy cảm hứng từ Smar , mượn từ Exherbo )git am
  • Thêm lịch sử cam kết của các tập tin sao chép / di chuyển
  • Từ thư mục này sang thư mục khác
  • Hoặc từ kho này sang kho khác

Giới hạn

  • Thẻ và chi nhánh không được lưu giữ
  • Lịch sử bị cắt trên tên đường dẫn đổi tên (đổi tên thư mục)

Tóm lược

  1. Trích xuất lịch sử ở định dạng email bằng cách sử dụng
    git log --pretty=email -p --reverse --full-index --binary
  2. Sắp xếp lại cây tập tin và cập nhật tên tệp
  3. Nối lịch sử mới bằng cách sử dụng
    cat extracted-history | git am --committer-date-is-author-date

1. Trích xuất lịch sử ở định dạng email

Ví dụ: Trích xuất lịch sử của file3, file4file5

my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

Đặt / làm sạch đích

export historydir=/tmp/mail/dir       # Absolute path
rm -rf "$historydir"    # Caution when cleaning the folder

Trích xuất lịch sử của từng tệp ở định dạng email

cd my_repo/dirB
find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "$0" > "$historydir/$0"' {} ';'

Thật không may tùy chọn --followhoặc --find-copies-harderkhông thể được kết hợp với --reverse. Đây là lý do tại sao lịch sử bị cắt khi tập tin được đổi tên (hoặc khi thư mục cha được đổi tên).

Lịch sử tạm thời ở định dạng email:

/tmp/mail/dir
    ├── subdir
    │   ├── file3
    │   └── file4
    └── file5

Dan Bonachea đề nghị đảo ngược các vòng lặp của lệnh tạo nhật ký git trong bước đầu tiên này: thay vì chạy nhật ký git một lần cho mỗi tệp, chạy chính xác một lần với danh sách các tệp trên dòng lệnh và tạo một nhật ký thống nhất. Cách này cam kết sửa đổi nhiều tệp vẫn là một cam kết duy nhất trong kết quả và tất cả các cam kết mới duy trì thứ tự tương đối ban đầu của chúng. Lưu ý điều này cũng yêu cầu thay đổi ở bước thứ hai bên dưới khi viết lại tên tệp trong nhật ký (hiện đã thống nhất).


2. Sắp xếp lại cây tập tin và cập nhật tên tệp

Giả sử bạn muốn di chuyển ba tệp này trong repo khác này (có thể là cùng một repo).

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB              # New tree
│   ├── dirB1         # from subdir
│   │   ├── file33    # from file3
│   │   └── file44    # from file4
│   └── dirB2         # new dir
│        └── file5    # from file5
└── dirH
    └── file77

Do đó, sắp xếp lại các tệp của bạn:

cd /tmp/mail/dir
mkdir -p dirB/dirB1
mv subdir/file3 dirB/dirB1/file33
mv subdir/file4 dirB/dirB1/file44
mkdir -p dirB/dirB2
mv file5 dirB/dirB2

Lịch sử tạm thời của bạn là bây giờ:

/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

Thay đổi tên tập tin trong lịch sử:

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'

3. Áp dụng lịch sử mới

Repo khác của bạn là:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
└── dirH
    └── file77

Áp dụng các cam kết từ các tệp lịch sử tạm thời:

cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am --committer-date-is-author-date

--committer-date-is-author-dategiữ nguyên dấu thời gian cam kết ban đầu ( bình luận của Dan Bonachea ).

Repo khác của bạn bây giờ là:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB
│   ├── dirB1
│   │   ├── file33
│   │   └── file44
│   └── dirB2
│        └── file5
└── dirH
    └── file77

Sử dụng git statusđể xem số lượng cam kết sẵn sàng được đẩy :-)


Thêm mẹo: Kiểm tra các tập tin được đổi tên / di chuyển trong repo của bạn

Để liệt kê các tập tin đã được đổi tên:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

Thêm tùy chỉnh: Bạn có thể hoàn thành lệnh git logbằng các tùy chọn --find-copies-harderhoặc --reverse. Bạn cũng có thể xóa hai cột đầu tiên bằng cách sử dụng cut -f3-và lấy mẫu hoàn chỉnh '{. * =>. *}'.

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'

4
THƯỞNG: Kỹ thuật này phân chia các cam kết thay đổi 2 hoặc nhiều tệp thành các cam kết phân mảnh riêng biệt và hơn nữa làm xáo trộn thứ tự của chúng bằng cách sắp xếp theo tên tệp (vì vậy các đoạn của một cam kết ban đầu không xuất hiện liền kề trong lịch sử tuyến tính). Do đó, lịch sử kết quả chỉ là "chính xác" trên cơ sở từng tệp. Nếu bạn đang di chuyển nhiều hơn một tệp, thì KHÔNG phải các cam kết mới trong lịch sử kết quả thể hiện một ảnh chụp nhanh nhất quán của các tệp đã di chuyển tồn tại trong lịch sử của repo gốc.
Dan Bonachea

2
Xin chào @DanBonachea. Cảm ơn bạn đã phản hồi thú vị của bạn. Tôi đã di chuyển thành công một số repos chứa một số tệp bằng kỹ thuật này (ngay cả với các tệp được đổi tên và các tệp được di chuyển qua các thư mục). Bạn đề nghị gì để thay đổi trong câu trả lời này. Bạn có nghĩ rằng chúng ta nên thêm một biểu ngữ CẢNH BÁO ở đầu câu trả lời này để giải thích những hạn chế của kỹ thuật này không? Chúc mừng
olibre

2
Tôi đã điều chỉnh kỹ thuật này để tránh sự cố bằng cách đảo ngược các vòng lặp của lệnh tạo nhật ký git trong bước 1. Tức là. thay vì chạy nhật ký git một lần trên mỗi tệp, hãy chạy chính xác một lần với danh sách các tệp trên dòng lệnh và tạo một nhật ký thống nhất. Cách này cam kết sửa đổi 2 hoặc nhiều tệp vẫn là một cam kết duy nhất trong kết quả và tất cả các cam kết mới duy trì thứ tự tương đối ban đầu của chúng. Lưu ý điều này cũng yêu cầu thay đổi ở bước 2 khi viết lại tên tệp trong nhật ký (hiện đã thống nhất). Tôi cũng đã sử dụng git am --committer-date-is-Author-date để lưu giữ dấu thời gian cam kết ban đầu.
Dan Bonachea

1
Cảm ơn bạn đã thử nghiệm và chia sẻ của bạn. Tôi đã cập nhật một chút câu trả lời cho các độc giả khác. Tuy nhiên tôi đã dành thời gian để kiểm tra xử lý của bạn. Xin vui lòng chỉnh sửa câu trả lời này nếu bạn muốn cung cấp các ví dụ về dòng lệnh. Chúc mừng;)
olibre

4

Tôi đã làm theo quy trình gồm nhiều bước này để di chuyển mã vào thư mục mẹ và giữ lại lịch sử.

Bước 0: Tạo một nhánh 'lịch sử' từ 'chính chủ' để giữ an toàn

Bước 1: Sử dụng công cụ git-filter-repo để viết lại lịch sử. Lệnh này bên dưới đã chuyển thư mục 'FolderwithContentOfInterest' lên một cấp và sửa đổi lịch sử cam kết có liên quan

git filter-repo --path-rename ParentFolder/FolderwithContentOfInterest/:FolderwithContentOfInterest/ --force

Bước 2: Đến lúc này kho GitHub bị mất đường dẫn kho lưu trữ từ xa. Đã thêm tham chiếu từ xa

git remote add origin git@github.com:MyCompany/MyRepo.git

Bước 3: Kéo thông tin trên kho lưu trữ

git pull

Bước 4: Kết nối nhánh bị mất cục bộ với nhánh gốc

git branch --set-upstream-to=origin/history history

Bước 5: Xung đột hợp nhất địa chỉ cho cấu trúc thư mục nếu được nhắc

Bước 6: Đẩy !!

git push

Lưu ý: Lịch sử đã sửa đổi và thư mục di chuyển dường như đã được cam kết. enter code here

Làm xong. Mã di chuyển đến thư mục cha / mong muốn giữ nguyên lịch sử!


2

Trong khi cốt lõi của Git, hệ thống ống nước Git không theo dõi các lần đổi tên, lịch sử bạn hiển thị với nhật ký Git "sứ" có thể phát hiện ra chúng nếu bạn muốn.

Đối với một git logsử dụng nhất định , tùy chọn -M:

git log -p -M

Với phiên bản hiện tại của Git.

Điều này làm việc cho các lệnh khác như git difflà tốt.

Có những lựa chọn để làm cho sự so sánh ít nhiều nghiêm ngặt hơn. Nếu bạn đổi tên một tệp mà không thực hiện thay đổi đáng kể cho tệp cùng một lúc, nó sẽ giúp nhật ký Git và bạn bè dễ dàng phát hiện đổi tên hơn. Vì lý do này, một số người đổi tên các tệp trong một cam kết và thay đổi chúng trong một cam kết khác.

Có một chi phí trong việc sử dụng CPU bất cứ khi nào bạn yêu cầu Git tìm nơi các tệp đã được đổi tên, do đó, liệu bạn có sử dụng nó hay không, và khi nào, tùy thuộc vào bạn.

Nếu bạn muốn luôn có báo cáo lịch sử của mình với phát hiện đổi tên trong một kho lưu trữ cụ thể, bạn có thể sử dụng:

git config diff.renames 1

Các tập tin di chuyển từ thư mục này sang thư mục khác được phát hiện. Đây là một ví dụ:

commit c3ee8dfb01e357eba1ab18003be1490a46325992
Author: John S. Gruber <JohnSGruber@gmail.com>
Date:   Wed Feb 22 22:20:19 2017 -0500

    test rename again

diff --git a/yyy/power.py b/zzz/power.py
similarity index 100%
rename from yyy/power.py
rename to zzz/power.py

commit ae181377154eca800832087500c258a20c95d1c3
Author: John S. Gruber <JohnSGruber@gmail.com>
Date:   Wed Feb 22 22:19:17 2017 -0500

    rename test

diff --git a/power.py b/yyy/power.py
similarity index 100%
rename from power.py
rename to yyy/power.py

Xin lưu ý rằng điều này hoạt động bất cứ khi nào bạn đang sử dụng diff, không chỉ với git log. Ví dụ:

$ git diff HEAD c3ee8df
diff --git a/power.py b/zzz/power.py
similarity index 100%
rename from power.py
rename to zzz/power.py

Khi dùng thử, tôi đã thực hiện một thay đổi nhỏ trong một tệp trong nhánh tính năng và cam kết nó và sau đó trong nhánh chính tôi đã đổi tên tệp, cam kết và sau đó thực hiện một thay đổi nhỏ trong một phần khác của tệp và cam kết điều đó. Khi tôi đi đến nhánh tính năng và được hợp nhất từ ​​chủ, hợp nhất đã đổi tên tệp và hợp nhất các thay đổi. Đây là đầu ra từ hợp nhất:

 $ git merge -v master
 Auto-merging single
 Merge made by the 'recursive' strategy.
  one => single | 4 ++++
  1 file changed, 4 insertions(+)
  rename one => single (67%)

Kết quả là một thư mục làm việc với tập tin được đổi tên và cả hai thay đổi văn bản được thực hiện. Vì vậy, Git có thể làm điều đúng mặc dù thực tế là nó không theo dõi rõ ràng việc đổi tên.

Đây là một câu trả lời muộn cho một câu hỏi cũ vì vậy các câu trả lời khác có thể đã đúng cho phiên bản Git tại thời điểm đó.


1

Đầu tiên tạo một cam kết độc lập chỉ với một đổi tên.

Sau đó, bất kỳ thay đổi cuối cùng đối với nội dung tập tin được đưa vào cam kết riêng.


1

Để đổi tên thư mục hoặc tệp (Tôi không biết nhiều về các trường hợp phức tạp, vì vậy có thể có một số cảnh báo):

git filter-repo --path-rename OLD_NAME:NEW_NAME

Để đổi tên một thư mục trong các tệp có đề cập đến nó (có thể sử dụng các cuộc gọi lại, nhưng tôi không biết làm thế nào):

git filter-repo --replace-text expressions.txt

expressions.txtlà một tệp chứa đầy các dòng như literal:OLD_NAME==>NEW_NAME(có thể sử dụng RE của Python với regex:hoặc global with glob:).

Để đổi tên một thư mục trong thông điệp cam kết:

git-filter-repo --message-callback 'return message.replace(b"OLD_NAME", b"NEW_NAME")'

Các biểu thức chính quy của Python cũng được hỗ trợ, nhưng chúng phải được viết bằng Python, theo cách thủ công.

Nếu kho lưu trữ là bản gốc, không có điều khiển từ xa, bạn sẽ phải thêm --forceđể buộc viết lại. (Bạn có thể muốn tạo bản sao lưu kho lưu trữ của mình trước khi thực hiện việc này.)

Nếu bạn không muốn duy trì refs (chúng sẽ được hiển thị trong lịch sử chi nhánh của GUI Git), bạn sẽ phải thêm --replace-refs delete-no-add.


0

Chỉ cần di chuyển tệp và giai đoạn với:

git add .

Trước khi cam kết, bạn có thể kiểm tra trạng thái:

git status

Điều đó sẽ hiển thị:

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        renamed:    old-folder/file.txt -> new-folder/file.txt

Tôi đã thử nghiệm với Git phiên bản 2.26.1.

Trích xuất từ Trang Trợ giúp GitHub .


-3

Tôi thực hiện di chuyển các tập tin và sau đó làm

git add -A

trong đó đặt trong khu vực bão hòa tất cả các tập tin bị xóa / mới. Ở đây git nhận ra rằng tập tin được di chuyển.

git commit -m "my message"
git push

Tôi không biết tại sao nhưng điều này làm việc cho tôi.


Mẹo ở đây là bạn không phải thay đổi một chữ cái, ngay cả khi bạn thay đổi thứ gì đó và nhấn Ctrl + Z, lịch sử sẽ bị phá vỡ. Vì vậy, trong trường hợp đó nếu bạn viết một cái gì đó hoàn nguyên tệp, và di chuyển nó một lần nữa và thực hiện một lần thêm-> cam kết cho nó.
Xelian
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.