Câu trả lời này cung cấp các lệnh thú vị dựa trên git am
và được trình bày bằng các ví dụ, từng bước một.
Mục tiêu
- Bạn muốn di chuyển một số hoặc tất cả các tệp từ kho này sang kho khác.
- Bạn muốn giữ lịch sử của họ.
- Nhưng bạn không quan tâm đến việc giữ thẻ và chi nhánh.
- Bạn chấp nhận lịch sử giới hạn cho các tệp được đổi tên (và các tệp trong thư mục được đổi tên).
Thủ tục
- 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
- Sắp xếp lại cây tập tin và cập nhật thay đổi tên tệp trong lịch sử [tùy chọn]
- Áp dụng lịch sử mới bằng cách sử dụng
git am
1. Trích xuất lịch sử ở định dạng email
Ví dụ: Trích xuất lịch sử của file3
, file4
vàfile5
my_repo
├── dirA
│ ├── file1
│ └── file2
├── dirB ^
│ ├── subdir | To be moved
│ │ ├── file3 | with history
│ │ └── file4 |
│ └── file5 v
└── dirC
├── file6
└── file7
Làm sạch đích thư mục tạm thời
export historydir=/tmp/mail/dir # Absolute path
rm -rf "$historydir" # Caution when cleaning
Làm sạch nguồn repo của bạn
git commit ... # Commit your working files
rm .gitignore # Disable gitignore
git clean -n # Simulate removal
git clean -f # Remove untracked file
git checkout .gitignore # Restore gitignore
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 --follow
hoặc --find-copies-harder
khô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 mẹ được đổi tên).
Sau: Lịch sử tạm thời ở định dạng email
/tmp/mail/dir
├── subdir
│ ├── file3
│ └── file4
└── file5
2. Sắp xếp lại cây tập tin và cập nhật thay đổi tên tệp trong lịch sử [tùy chọn]
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 # was subdir
│ │ ├── file33 # was file3
│ │ └── file44 # was file4
│ └── dirB2 # new dir
│ └── file5 # = file5
└── dirH
└── file77
Do đó, sắp xếp lại các tệp của bạn:
cd /tmp/mail/dir
mkdir dirB
mv subdir dirB/dirB1
mv dirB/dirB1/file3 dirB/dirB1/file33
mv dirB/dirB1/file4 dirB/dirB1/file44
mkdir 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"' {} ';'
Lưu ý: Điều này viết lại lịch sử để phản ánh sự thay đổi của đường dẫn và tên tệp.
(tức là thay đổi vị trí / tên mới trong repo mới)
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
Repo khác của bạn bây giờ là:
my_other_repo
├── dirF
│ ├── file55
│ └── file56
├── dirB ^
│ ├── dirB1 | New files
│ │ ├── file33 | with
│ │ └── file44 | history
│ └── dirB2 | kept
│ └── file5 v
└── dirH
└── file77
Sử dụng git status
để xem số lượng cam kết sẵn sàng được đẩy :-)
Lưu ý: Vì lịch sử đã được viết lại để phản ánh đường dẫn và thay đổi tên tệp:
(nghĩa là so với vị trí / tên trong repo trước đó)
- Không cần phải
git mv
thay đổi vị trí / tên tệp.
- Không cần phải
git log --follow
truy cập đầy đủ lịch sử.
Thêm mẹo: Phát hiện các tệp đã đổ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 log
bằng các tùy chọn --find-copies-harder
hoặ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 '{.* => .*}'