Trong trường hợp nào `git pull` có thể gây hại?


409

Tôi có một đồng nghiệp tuyên bố rằng git pullnó có hại, và buồn bã mỗi khi ai đó sử dụng nó.

Các git pulllệnh có vẻ là một cách kinh điển để cập nhật kho địa phương của bạn. Có sử dụng git pulltạo ra vấn đề? Nó tạo ra vấn đề gì? Có cách nào tốt hơn để cập nhật kho git không?



8
Hoặc bạn có thể chỉ git pull --rebasevà đặt chiến lược này làm mặc định cho các chi nhánh mới git config branch.autosetuprebase
knoopx

4
knoopx có quyền, thêm --rebasecờ để git pullđồng bộ hóa cục bộ với điều khiển từ xa sau đó phát lại các thay đổi cục bộ của bạn trên đầu trang cục bộ được cập nhật. Sau đó, khi bạn đẩy tất cả những gì bạn đang làm là nối các cam kết mới của bạn vào cuối điều khiển từ xa. Khá đơn giản.
Heath Lilley

4
Cảm ơn @BenMcCormick. Tôi đã làm điều đó, nhưng cuộc thảo luận về tính hợp lệ của câu hỏi dường như đang diễn ra trong những bình luận bên dưới câu hỏi. Và tôi nghĩ rằng việc đặt câu hỏi để tạo ra một nền tảng để trình bày ý kiến ​​cá nhân của bạn vì thực tế không phải là cấu trúc Q & A của SO thực sự dành cho.
mcv

4
@RichardHansen, nó chỉ giống như một cách để gian lận hệ thống điểm, đặc biệt là với câu trả lời của bạn có sự khác biệt lớn về giọng điệu và khoảng cách thời gian ngắn như vậy. Sử dụng mô hình Hỏi và Đáp của bạn, tất cả chúng ta có thể chỉ cần đặt câu hỏi và tự trả lời chúng bằng các kiến ​​thức trước đây của chúng tôi. Tại thời điểm đó, bạn chỉ nên xem xét viết một bài đăng blog vì nó phù hợp hơn nhiều lần. Q & A đặc biệt tìm kiếm kiến ​​thức của người khác. Một bài đăng blog trưng bày của riêng bạn.
Josh Brown

Câu trả lời:


546

Tóm lược

Theo mặc định, git pull tạo các xác nhận hợp nhất sẽ thêm nhiễu và độ phức tạp vào lịch sử mã. Ngoài ra, pullgiúp bạn dễ dàng không nghĩ về những thay đổi của bạn có thể bị ảnh hưởng bởi những thay đổi đến.

Các git pulllệnh là an toàn quá lâu vì nó chỉ thực hiện hòa trộn nhanh về phía trước. Nếugit pull được định cấu hình để chỉ thực hiện hợp nhất chuyển tiếp nhanh và khi không thể hợp nhất chuyển tiếp nhanh, thì Git sẽ thoát với một lỗi. Điều này sẽ cho bạn cơ hội nghiên cứu các cam kết đến, suy nghĩ về cách chúng có thể ảnh hưởng đến các cam kết cục bộ của bạn và quyết định hướng hành động tốt nhất (hợp nhất, rebase, thiết lập lại, v.v.).

Với Git 2.0 và mới hơn, bạn có thể chạy:

git config --global pull.ff only

để thay đổi hành vi mặc định thành chỉ chuyển tiếp nhanh. Với các phiên bản Git trong khoảng 1.6.6 đến 1.9.x, bạn sẽ phải tập thói quen gõ:

git pull --ff-only

Tuy nhiên, với tất cả các phiên bản của Git, tôi khuyên bạn nên định cấu hình git upbí danh như thế này:

git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'

và sử dụng git upthay vì git pull. Tôi thích bí danh này hơn git pull --ff-onlybởi vì:

  • nó hoạt động với tất cả các phiên bản (không cổ) của Git,
  • nó tìm nạp tất cả các nhánh ngược dòng (không chỉ là nhánh bạn đang làm việc) và
  • nó làm sạch origin/*các nhánh cũ không còn tồn tại ở thượng nguồn.

Có vấn đề với git pull

git pullKhông tệ nếu nó được sử dụng đúng cách. Một số thay đổi gần đây đối với Git đã giúp sử dụng git pullđúng cách dễ dàng hơn , nhưng thật không may, hành vi mặc định của đồng bằng git pullcó một số vấn đề:

  • nó giới thiệu những phi tuyến không cần thiết trong lịch sử
  • nó làm cho nó dễ dàng vô tình giới thiệu lại các cam kết đã được cố tình từ chối ngược dòng
  • nó sửa đổi thư mục làm việc của bạn theo những cách không thể đoán trước
  • tạm dừng những gì bạn đang làm để xem xét công việc của người khác gây phiền nhiễu với git pull
  • nó làm cho nó khó có thể nổi loạn chính xác vào nhánh từ xa
  • nó không dọn sạch các nhánh đã bị xóa trong repo từ xa

Những vấn đề này được mô tả chi tiết hơn dưới đây.

Lịch sử phi tuyến

Theo mặc định, git pulllệnh tương đương với việc chạy git fetchtheo sau git merge @{u}. Nếu có các xác nhận chưa được đánh dấu trong kho lưu trữ cục bộ, phần hợp nhất của việc git pulltạo một cam kết hợp nhất.

Không có gì xấu về các cam kết hợp nhất, nhưng chúng có thể nguy hiểm và cần được đối xử tôn trọng:

  • Hợp nhất cam kết vốn đã khó kiểm tra. Để hiểu những gì một sự hợp nhất đang làm, bạn phải hiểu sự khác biệt cho tất cả các bậc cha mẹ. Một khác thường thông thường không truyền tải thông tin đa chiều này tốt. Ngược lại, một loạt các cam kết bình thường rất dễ xem xét.
  • Hợp nhất giải quyết xung đột là khó khăn và các lỗi thường không bị phát hiện trong một thời gian dài vì các cam kết hợp nhất rất khó để xem xét.
  • Sáp nhập có thể lặng lẽ thay thế các hiệu ứng của cam kết thông thường. Mã không còn là tổng của các cam kết gia tăng, dẫn đến sự hiểu lầm về những gì thực sự thay đổi.
  • Hợp nhất cam kết có thể phá vỡ một số lược đồ tích hợp liên tục (ví dụ: chỉ tự động xây dựng đường dẫn cha mẹ đầu tiên theo quy ước giả định rằng cha mẹ thứ hai chỉ ra các công việc chưa hoàn thành đang tiến hành).

Tất nhiên có một thời gian và một nơi để hợp nhất, nhưng sự hiểu biết khi hợp nhất nên và không nên được sử dụng có thể cải thiện tính hữu ích của kho lưu trữ của bạn.

Lưu ý rằng mục đích của Git là giúp dễ dàng chia sẻ và tiêu thụ sự phát triển của một cơ sở mã, không ghi lại chính xác lịch sử chính xác như đã diễn ra. (Nếu bạn không đồng ý, hãy xem xét rebaselệnh và lý do tại sao nó được tạo.) Các cam kết hợp nhất được tạo bằng cách git pullkhông truyền đạt ngữ nghĩa hữu ích cho người khác. Họ chỉ nói rằng ai đó đã tình cờ đẩy vào kho lưu trữ trước khi bạn thực hiện xong các thay đổi của mình. Tại sao những cam kết hợp nhất nếu chúng không có ý nghĩa với người khác và có thể nguy hiểm?

Có thể cấu hình git pullđể rebase thay vì hợp nhất, nhưng điều này cũng có vấn đề (sẽ thảo luận sau). Thay vào đó, git pullnên được cấu hình để chỉ thực hiện hợp nhất chuyển tiếp nhanh.

Giới thiệu lại các cam kết bị loại bỏ

Giả sử ai đó khởi động lại một nhánh và lực đẩy nó. Điều này thường không nên xảy ra, nhưng đôi khi cần thiết (ví dụ: để xóa tệp nhật ký 50GiB đã vô tình được gửi và đẩy). Việc hợp nhất được thực hiện bởi git pullsẽ hợp nhất phiên bản mới của nhánh ngược dòng vào phiên bản cũ vẫn còn tồn tại trong kho lưu trữ cục bộ của bạn. Nếu bạn đẩy kết quả, dĩa và ngọn đuốc sẽ bắt đầu theo cách của bạn.

Một số người có thể lập luận rằng vấn đề thực sự là cập nhật lực lượng. Có, nói chung nên tránh đẩy lực bất cứ khi nào có thể, nhưng đôi khi chúng không thể tránh khỏi. Các nhà phát triển phải được chuẩn bị để đối phó với các cập nhật lực lượng, bởi vì đôi khi chúng sẽ xảy ra. Điều này có nghĩa là không hợp nhất một cách mù quáng trong các cam kết cũ thông qua một thông thường git pull.

Điều chỉnh thư mục làm việc bất ngờ

Không có cách nào để dự đoán thư mục hoặc chỉ mục làm việc sẽ trông như thế nào cho đến khi git pullhoàn thành. Có thể có các xung đột hợp nhất mà bạn phải giải quyết trước khi bạn có thể làm bất cứ điều gì khác, nó có thể giới thiệu tệp nhật ký 50GiB trong thư mục làm việc của bạn vì ai đó đã vô tình đẩy nó, nó có thể đổi tên một thư mục bạn đang làm việc, v.v.

git remote update -p(hoặc git fetch --all -p) cho phép bạn xem xét các cam kết của người khác trước khi bạn quyết định hợp nhất hoặc nổi loạn, cho phép bạn lập kế hoạch trước khi thực hiện hành động.

Khó khăn khi xem xét các cam kết khác của nhân dân

Giả sử bạn đang thực hiện một số thay đổi và người khác muốn bạn xem lại một số cam kết họ vừa đẩy. git pullHoạt động hợp nhất (hoặc rebase) sửa đổi thư mục và chỉ mục làm việc, có nghĩa là thư mục và chỉ mục làm việc của bạn phải sạch.

Bạn có thể sử dụng git stashvà sau đó git pull, nhưng bạn sẽ làm gì khi xem xét xong? Để quay lại nơi bạn đã ở, bạn phải hoàn tác việc hợp nhất được tạo bởi git pullvà áp dụng stash.

git remote update -p(hoặc git fetch --all -p) không sửa đổi thư mục hoặc chỉ mục làm việc, do đó, an toàn để chạy bất cứ lúc nào, ngay cả khi bạn đã thay đổi và / hoặc thay đổi không theo giai đoạn. Bạn có thể tạm dừng những gì bạn đang làm và xem xét cam kết của người khác mà không phải lo lắng về việc xóa hoặc hoàn thành cam kết mà bạn đang thực hiện.git pullkhông cung cấp cho bạn sự linh hoạt.

Nổi loạn lên một Chi nhánh từ xa

Một mô hình sử dụng Git phổ biến là thực hiện git pullđể mang lại những thay đổi mới nhất theo sau là git rebase @{u}loại bỏ cam kết hợp nhất được git pullgiới thiệu. Nó đủ phổ biến mà Git có một số tùy chọn cấu hình để giảm hai bước vào một bước duy nhất bằng cách nói git pullđể thực hiện một rebase thay vì một hợp nhất (xem branch.<branch>.rebase, branch.autosetuprebasepull.rebasetùy chọn).

Thật không may, nếu bạn có một cam kết hợp nhất chưa được đánh dấu mà bạn muốn giữ lại (ví dụ: một cam kết hợp nhất một nhánh tính năng được đẩy vào master), không phải là một rebase-pull ( git pullvới branch.<branch>.rebaseđược đặt thành true) cũng không phải là một pull-pull ( git pullhành vi mặc định ) theo sau rebase sẽ hoạt động. Điều này là do git rebaseloại bỏ sự hợp nhất (nó tuyến tính hóa DAG) mà không có --preserve-mergestùy chọn. Hoạt động kéo lại rebase không thể được cấu hình để duy trì các phép hợp nhất và một phép kéo hợp nhất theo sau là git rebase -p @{u}sẽ không loại bỏ sự hợp nhất gây ra bởi phép kéo hợp nhất. Cập nhật: Đã thêm Git v1.8.5 git pull --rebase=preservegit config pull.rebase preserve. Những nguyên nhân git pullphải làm git rebase --preserve-mergessau khi tìm nạp các cam kết ngược dòng. (Cảm ơn funkaster hỗ trợ!)

Dọn dẹp các chi nhánh đã xóa

git pullkhông cắt tỉa các nhánh theo dõi từ xa tương ứng với các nhánh đã bị xóa khỏi kho lưu trữ từ xa. Ví dụ: nếu ai đó xóa chi nhánh fookhỏi repo từ xa, bạn vẫn sẽ thấy origin/foo.

Điều này dẫn đến việc người dùng vô tình hồi sinh các chi nhánh bị giết vì họ nghĩ rằng chúng vẫn còn hoạt động.

Một thay thế tốt hơn: Sử dụng git upthay vìgit pull

Thay vì git pull, tôi khuyên bạn nên tạo và sử dụng git upbí danh sau :

git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'

Bí danh này tải xuống tất cả các cam kết mới nhất từ ​​tất cả các nhánh ngược dòng (cắt tỉa các nhánh chết) và cố gắng chuyển nhanh chi nhánh địa phương đến cam kết mới nhất trên nhánh ngược dòng. Nếu thành công, sau đó không có cam kết địa phương, do đó không có nguy cơ xung đột hợp nhất. Chuyển tiếp nhanh sẽ thất bại nếu có các cam kết cục bộ (chưa được xử lý), cho bạn cơ hội để xem xét các cam kết ngược dòng trước khi thực hiện hành động.

Điều này vẫn sửa đổi thư mục làm việc của bạn theo những cách không thể đoán trước, nhưng chỉ khi bạn không có bất kỳ thay đổi cục bộ nào. Không giống như git pull, git upsẽ không bao giờ thả bạn đến một dấu nhắc mong đợi bạn khắc phục xung đột hợp nhất.

Một lựa chọn khác: git pull --ff-only --all -p

Sau đây là một thay thế cho git upbí danh trên :

git config --global alias.up 'pull --ff-only --all -p'

Phiên bản git upnày có hành vi tương tự như git upbí danh trước đó , ngoại trừ:

  • thông báo lỗi khó hiểu hơn một chút nếu nhánh cục bộ của bạn không được cấu hình với nhánh ngược dòng
  • nó dựa vào một tính năng không có giấy tờ ( -pđối số, được chuyển đến fetch) có thể thay đổi trong các phiên bản tương lai của Git

Nếu bạn đang chạy Git 2.0 hoặc mới hơn

Với Git 2.0 và mới hơn, bạn có thể định cấu hình git pullđể chỉ thực hiện hợp nhất chuyển tiếp nhanh theo mặc định:

git config --global pull.ff only

Điều này gây ra git pullhành động như vậy git pull --ff-only, nhưng nó vẫn không tìm nạp tất cả các cam kết ngược dòng hoặc dọn sạch origin/*các nhánh cũ vì vậy tôi vẫn thích git up.


6
@brianz: git remote update -ptương đương với git fetch --all -p. Tôi có thói quen đánh máy git remote update -pvì ngày fetchxưa không có -ptùy chọn. Về hàng đầu !, xem mô tả alias.*trong git help config. Nó nói, "Nếu việc mở rộng bí danh có tiền tố với dấu chấm than, nó sẽ được coi là một lệnh shell."
Richard Hansen

13
Git 2.0 thêm một pull.ffcấu hình dường như để đạt được điều tương tự, không có bí danh.
Daniel Thomas

51
Một số lý do nghe có vẻ như "kéo có thể gây ra vấn đề khi những người khác làm những thứ điên rồ". Không, đó là những thứ điên rồ như loại bỏ một cam kết ra khỏi một repo ngược dòng gây ra vấn đề. IMO rebase chỉ an toàn khi bạn thực hiện cục bộ trên một cam kết chưa được đẩy. Ví dụ như khi bạn kéo trước khi đẩy, việc khởi động lại các cam kết cục bộ giúp giữ cho lịch sử của bạn tuyến tính (mặc dù lịch sử tuyến tính không phải là vấn đề lớn). Tuy nhiên, git upâm thanh như một sự thay thế thú vị.
mcv

16
Hầu hết các điểm của bạn là do bạn đang làm sai điều gì đó: bạn đang cố gắng xem lại mã trong nhánh làm việc của riêng bạn . Đó không phải là một ý tưởng tốt, chỉ cần tạo một nhánh mới, kéo --rebase = bảo tồn và sau đó ném nhánh đó (hoặc hợp nhất nó nếu bạn muốn).
funkaster

5
Quan điểm của @ funkaster ở đây rất có ý nghĩa, đặc biệt là: "Khó khăn khi xem xét các cam kết của người khác". Đây không phải là luồng đánh giá mà hầu hết người dùng Git sử dụng, đó là điều tôi chưa từng thấy được đề xuất ở bất cứ đâu và đó là nguyên nhân của tất cả các công việc phụ không cần thiết được mô tả bên dưới tiêu đề, không phải git pull.
Ben Regenspan

195

Câu trả lời của tôi, rút ​​ra từ cuộc thảo luận nảy sinh trên HackerNews:

Tôi cảm thấy bị cám dỗ khi chỉ trả lời câu hỏi bằng cách sử dụng Luật Tiêu đề Betteridge: Tại sao được git pullcoi là có hại? Không phải vậy.

  • Phi tuyến thực chất không xấu. Nếu họ đại diện cho lịch sử thực tế, họ là ok.
  • Vô tình giới thiệu lại các cam kết bị từ chối thượng nguồn là kết quả của sai viết lại lịch sử thượng nguồn. Bạn không thể viết lại lịch sử khi lịch sử được sao chép dọc theo một số repos.
  • Sửa đổi thư mục làm việc là một kết quả mong đợi; về tính hữu dụng gây tranh cãi, cụ thể là khi đối mặt với hành vi của hg / monotone / darcs / other_dvcs_predating_git, nhưng về bản chất lại không tệ.
  • Tạm dừng để xem xét công việc của người khác là cần thiết để hợp nhất, và một lần nữa là một hành vi dự kiến ​​trên git pull. Nếu bạn không muốn hợp nhất, bạn nên sử dụng git fetch. Một lần nữa, đây là một đặc điểm riêng của git so với các dvcs phổ biến trước đây, nhưng nó là hành vi được mong đợi và về bản chất không phải là xấu.
  • Làm cho nó khó để chống lại một chi nhánh từ xa là tốt. Đừng viết lại lịch sử trừ khi bạn thực sự cần. Cả đời tôi không thể hiểu được việc theo đuổi lịch sử tuyến tính (giả) này
  • Không dọn dẹp cành cây là tốt. Mỗi repo biết những gì nó muốn giữ. Git không có khái niệm về mối quan hệ chủ-nô.

13
Tôi đồng ý. Không có gì vốn có hại về git pull. Tuy nhiên, nó có thể mâu thuẫn với một số thực tiễn có hại, như muốn viết lại lịch sử nhiều hơn là rất cần thiết. Nhưng git là linh hoạt, vì vậy nếu bạn muốn sử dụng nó theo một cách khác, bằng mọi cách hãy làm như vậy. Nhưng đó là vì bạn (tốt, @Richard Hansen) muốn làm điều gì đó bất thường trong git, và không phải vì git pullcó hại.
mcv

28
Không thể đồng ý nhiều hơn. Mọi người đang ủng hộ git rebasevà xem xét git pullcó hại? Có thật không?
Victor Moroz

10
Thật tuyệt khi thấy ai đó tạo một biểu đồ, với đạo đức là trục của bạn và phân loại các lệnh git là tốt, xấu hoặc ở đâu đó ở giữa. Biểu đồ này sẽ khác nhau giữa các nhà phát triển, mặc dù nó sẽ nói rất nhiều về việc một người sử dụng git.
michaelt

5
Vấn đề của tôi git pullmà không có --rebasetùy chọn là hướng hợp nhất mà nó tạo ra. Khi bạn nhìn vào khác biệt, tất cả các thay đổi trong hợp nhất đó bây giờ thuộc về người đã kéo, thay vì người thực hiện các thay đổi. Tôi thích một quy trình công việc trong đó việc hợp nhất được dành riêng cho hai nhánh riêng biệt (A -> B) để cam kết hợp nhất rõ ràng những gì đã được giới thiệu và việc khởi động lại được dành riêng để cập nhật trên cùng một nhánh (từ xa A -> cục bộ A )
Craig Kochis

4
Vì vậy, điều gì khiến bạn biết nếu ai đó thực hiện một cú kéo chỉ vài giây trước khi người khác hoặc cách khác? Tôi nghĩ rằng đây chỉ là tiếng ồn và chỉ làm xáo trộn lịch sử thực sự có liên quan. Điều này thậm chí làm giảm giá trị của lịch sử. Một lịch sử tốt nên là a) sạch sẽ và b) thực sự có lịch sử quan trọng.
David Ongaro

26

Nó không được coi là có hại nếu bạn đang sử dụng Git chính xác. Tôi thấy nó ảnh hưởng tiêu cực đến trường hợp sử dụng của bạn như thế nào, nhưng bạn có thể tránh các vấn đề đơn giản bằng cách không sửa đổi lịch sử chia sẻ.


10
Để giải thích về điều này: Nếu mọi người làm việc trên nhánh riêng của họ (mà theo tôi là cách sử dụng git phù hợp), git pullthì đó không phải là bất kỳ vấn đề nào. Chi nhánh trong git là giá rẻ.
AlexQueue

18

Câu trả lời được chấp nhận

Hoạt động kéo rebase không thể được cấu hình để duy trì sự hợp nhất

nhưng kể từ Git 1.8.5 , trả lời câu trả lời đó, bạn có thể làm

git pull --rebase=preserve

hoặc là

git config --global pull.rebase preserve

hoặc là

git config branch.<name>.rebase preserve

Các tài liệu nói

Khi preserve,cũng chuyển qua --preserve-merges'git rebase' để các cam kết hợp nhất được cam kết cục bộ sẽ không bị làm phẳng bằng cách chạy 'git pull'.

Thảo luận trước này có thông tin và sơ đồ chi tiết hơn: git pull --rebase --preserve-merges . Nó cũng giải thích tại sao git pull --rebase=preservekhông giống như git pull --rebase --preserve-merges, điều này không làm đúng.

Cuộc thảo luận trước đó này giải thích biến thể rebase hợp nhất thực sự làm gì, và nó phức tạp hơn nhiều so với rebase thông thường: Chính xác thì "rebase --preserve-merges" của git làm gì (và tại sao?)


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.