วิธีการตั้งค่าคอมไพล์ - ฮาร์ดไดเรคทอรีย่อย?


197

UPDATE² : ด้วย Git 2.23 (สิงหาคม 2019) มีคำสั่งใหม่git restoreที่ไม่นี้ให้ดูที่คำตอบที่ได้รับการยอมรับ

UPDATE : นี้จะทำงานขึ้นอย่างสังหรณ์ใจเป็นของ Git 1.8.3 ดูคำตอบของฉันเอง

ลองนึกภาพกรณีการใช้งานต่อไปนี้: ฉันต้องการกำจัดการเปลี่ยนแปลงทั้งหมดในไดเรกทอรีย่อยที่เฉพาะเจาะจงของแผนผังการทำงาน Git ของฉันโดยปล่อยให้ไดเรกทอรีย่อยอื่น ๆ ไม่เสียหาย

คำสั่ง Git ที่เหมาะสมสำหรับการดำเนินการนี้คืออะไร?

สคริปต์ด้านล่างแสดงปัญหา แทรกคำสั่งที่เหมาะสมด้านล่างHow to make filesความคิดเห็น - คำสั่งปัจจุบันจะคืนค่าไฟล์a/c/acที่ควรจะแยกออกจากเช็คเอาต์กระจัดกระจาย โปรดทราบว่าฉันไม่ต้องการกู้คืนอย่างชัดเจนa/aและa/bฉันเพียง "รู้" aและต้องการคืนค่าทุกอย่างด้านล่าง แก้ไข : และฉันยังไม่ "รู้" bหรือที่ไดเรกทอรีอื่น ๆ aอยู่ในระดับเดียวกับ

#!/bin/sh

rm -rf repo; git init repo; cd repo
for f in a b; do
  for g in a b c; do
    mkdir -p $f/$g
    touch $f/$g/$f$g
    git add $f/$g
    git commit -m "added $f/$g"
  done
done
git config core.sparsecheckout true
echo a/a > .git/info/sparse-checkout
echo a/b >> .git/info/sparse-checkout
echo b/a >> .git/info/sparse-checkout
git read-tree -m -u HEAD
echo "After read-tree:"
find * -type f

rm a/a/aa
rm a/b/ab
echo >> b/a/ba
echo "After modifying:"
find * -type f
git status

# How to make files a/* reappear without changing b and without recreating a/c?
git checkout -- a

echo "After checkout:"
git status
find * -type f

3
เกี่ยวกับgit stash && git stash dropอะไร
CharlesB

1
แล้วgit checkout -- /path/to/subdir/ไงล่ะ
iberbeu

3
@CharlesB: git stashไม่ยอมรับอาร์กิวเมนต์พา ธ ...
krlmlr

@iberbeu: ไม่ จะเพิ่มไฟล์ที่ไม่รวมอยู่ในการชำระเงินเบาบาง
krlmlr

1
@CharlesBailey: แล้วทำไมถึงมีปุ่มตัวเลือกอ่าน "มองหาคำตอบที่วาดจากแหล่งข้อมูลที่น่าเชื่อถือและ / หรือเป็นทางการ" ในกล่องโต้ตอบเงินรางวัล? ฉันไม่ได้พิมพ์ด้วยตัวเอง! ลองใช้ googling "ไดเรกทอรีย่อยรีเซ็ต git" (โดยไม่ใส่เครื่องหมายอัญประกาศ) และดูว่ามีอะไรใน 3 ตำแหน่งแรก แน่นอนว่าข้อความไปยังรายชื่อผู้รับจดหมาย kernel.org จะค้นหาได้ยากขึ้น - สำหรับฉันมันยังไม่ชัดเจนหากลักษณะการทำงานนี้เป็นข้อบกพร่องหรือคุณลักษณะ
krlmlr

คำตอบ:


164

ด้วย Git 2.23 (สิงหาคม 2019) คุณมีคำสั่งใหม่git restore

git restore --source=HEAD --staged --worktree -- aDirectory
# or, shorter
git restore -s@ -SW  -- aDirectory

ซึ่งจะแทนที่ทั้งดัชนีและแผนผังการทำงานด้วยHEADเนื้อหาเช่นต้องการreset --hardแต่สำหรับเส้นทางที่เฉพาะเจาะจง


คำตอบเดิม (2013)

หมายเหตุ (ตามความเห็นโดยDan Fabulich ) ว่า:

  • git checkout -- <path> ไม่ได้ทำการฮาร์ดรีเซ็ต: มันจะแทนที่เนื้อหาแผนผังการทำงานด้วยเนื้อหาแบบฉาก
  • git checkout HEAD -- <path>ทำการฮาร์ดรีเซ็ตสำหรับพา ธ แทนที่ทั้งดัชนีและแผนผังการทำงานด้วยเวอร์ชันจากคอมHEADมิชชัน

ในฐานะที่ตอบโดยAjedi32รูปแบบทั้งการเช็คเอาต์จะไม่ลบไฟล์ที่ถูกลบในการแก้ไขเป้าหมาย
หากคุณมีไฟล์เพิ่มเติมในแผนผังการทำงานซึ่งไม่มีอยู่ใน HEAD a git checkout HEAD -- <path>จะไม่ลบไฟล์

หมายเหตุ: ด้วยgit checkout --overlay HEAD -- <path>(Git 2.22, Q1 2019)ไฟล์ที่ปรากฏในดัชนีและแผนผังการทำงาน แต่ไม่ใช่<tree-ish>จะถูกลบออกเพื่อให้ตรงกับไฟล์<tree-ish>ทั้งหมด

แต่การชำระเงินนั้นสามารถเคารพgit update-index --skip-worktree(สำหรับไดเรกทอรีที่คุณต้องการเพิกเฉย) ดังที่กล่าวไว้ใน " ทำไมไฟล์ที่ถูกแยกออกมาปรากฏขึ้นอีกครั้งในการชำระเงินเบาบางคอมไพล์ของฉัน "


1
กรุณาชี้แจง หลังจากนั้นgit checkout HEAD -- .ไฟล์ที่ถูกแยกออกโดยการชำระเงินแบบเบาบางจะปรากฏขึ้นอีกครั้ง สิ่งที่git update-index --skip-worktreeควรทำ
krlmlr

@krlmlr skip-worktree หรือสันนิษฐานว่าไม่มีการเปลี่ยนแปลงเป็นสองวิธีในการพยายามสร้างรายการในดัชนี "มองไม่เห็น" สำหรับ git: fallengamer.livejournal.com/93321.html , stackoverflow.com/q/13630849/6309และstackoverflow com / a / 6139470/6309
VonC

@krlmlr ลิงก์เหล่านั้นเป็นเพียงตัวชี้ให้คุณลองดูว่าการชำระเงินจะยังคงเรียกคืนรายการเหล่านั้นหรือไม่เมื่อพวกเขาถูกทำเครื่องหมายเป็น 'skipped-worktree'
VonC

ขออภัย แต่มันซับซ้อนเกินไปสำหรับภารกิจในมือ ฉันต้องการการรีเซ็ตแบบรวมไม่ใช่แบบเอกสิทธิ์ ไม่มีวิธีที่ดีในการทำเช่นนี้ใน Git หรือไม่?
krlmlr

@krlmlr no: ทำสิ่งที่ดีที่สุดgit checkout HEAD -- <path>แล้วลบไดเรกทอรีที่ถูกกู้คืน (แต่ยังประกาศในการชำระเงินแบบเบาบาง)
VonC

126

ตามที่นักพัฒนา Git Duy เหงียนผู้ใช้คุณสมบัติและสวิตช์ความเข้ากันได้กรุณาทำงานต่อไปตามที่คาดไว้ณ Git 1.8.3 :

git checkout -- a

(ซึ่งaเป็นไดเรกทอรีที่คุณต้องการฮาร์ดรีเซ็ต) พฤติกรรมดั้งเดิมสามารถเข้าถึงได้ผ่าน

git checkout --ignore-skip-worktree-bits -- a

5
ขอขอบคุณสำหรับความพยายามของคุณในการติดตามกับทีมพัฒนา Git ส่งผลให้เกิดการเปลี่ยนแปลงใน Git
Dan Cruz

13
และโปรดทราบว่า "a" ในกรณีนี้หมายถึงไดเรกทอรีที่คุณต้องการเปลี่ยนกลับดังนั้นหากคุณอยู่ในไดเรกทอรีที่คุณต้องการเปลี่ยนกลับคำสั่งควรเป็นgit checkout -- .ตำแหน่งที่.หมายถึงไดเรกทอรีปัจจุบัน
TheWestIsThe ...

5
หนึ่งความคิดเห็นจากด้านข้างของฉันคือคุณต้อง unstage โฟลเดอร์แรกที่มี git reset -- a(ที่เป็นไดเรกทอรีที่คุณต้องการรีเซ็ต)
Boyan

มันไม่จริงหรือถ้าคุณต้องการgit reset --hardซื้อคืนทั้งหมด?
krlmlr

และถ้าคุณเพิ่มไฟล์ใหม่ลงในไดเรกทอรีให้ทำrm -rf aก่อน
Tobias Feil

30

ลองเปลี่ยน

git checkout -- a

ถึง

git checkout -- `git ls-files -m -- a`

ตั้งแต่รุ่น 1.7.0 Git ให้เกียรติธง skip-worktreels-files เกียรตินิยมธงข้าม

เรียกใช้สคริปต์ทดสอบของคุณ (โดยมีการปรับแต่งเล็กน้อยเปลี่ยนgit commit... เป็นgit commit -qและgit statusไปยังgit status --short) ผลลัพธ์:

Initialized empty Git repository in /home/user/repo/.git/
After read-tree:
a/a/aa
a/b/ab
b/a/ba
After modifying:
b/a/ba
 D a/a/aa
 D a/b/ab
 M b/a/ba
After checkout:
 M b/a/ba
a/a/aa
a/c/ac
a/b/ab
b/a/ba

เรียกใช้สคริปต์ทดสอบของคุณด้วยcheckoutผลลัพธ์การเปลี่ยนแปลงที่เสนอ:

Initialized empty Git repository in /home/user/repo/.git/
After read-tree:
a/a/aa
a/b/ab
b/a/ba
After modifying:
b/a/ba
 D a/a/aa
 D a/b/ab
 M b/a/ba
After checkout:
 M b/a/ba
a/a/aa
a/b/ab
b/a/ba

ฟังดูเข้าท่า. แต่ไม่ควรgit checkoutเคารพบิต "skip-worktree" ในตอนแรกใช่ไหม
krlmlr

การตรวจสอบอย่างรวดเร็วcheckout.cและtree.cไม่เปิดเผยว่ามีการใช้แฟล็ก skip-worktree
Dan Cruz

นี่ง่ายพอที่จะเป็นประโยชน์ในทางปฏิบัติแม้ว่าฉันจะต้องตั้งค่า bash alias สำหรับคำสั่งนี้ Duy Nguyen ได้ตอบกลับข้อความของฉันไปยังรายชื่อผู้รับจดหมาย Git แล้วมาดูกันว่าอีกทางเลือกที่ใช้งานง่ายจะปรากฏขึ้นในไม่ช้า
krlmlr

18

สำหรับกรณีของการยกเลิกการเปลี่ยนแปลงเพียงอย่างเดียวคำสั่งgit checkout -- path/หรือgit checkout HEAD -- path/คำแนะนำที่แนะนำโดยคำตอบอื่น ๆ ใช้งานได้ดี อย่างไรก็ตามเมื่อคุณต้องการรีเซ็ตไดเรกทอรีเป็นรุ่นอื่นนอกเหนือจาก HEAD โซลูชันดังกล่าวมีปัญหาสำคัญ: จะไม่ลบไฟล์ที่ถูกลบในการแก้ไขเป้าหมาย

ดังนั้นฉันเริ่มใช้คำสั่งต่อไปนี้แทน:

git diff --cached commit -- subdir | git apply -R --index

สิ่งนี้ทำงานได้โดยการค้นหาความแตกต่างระหว่างการส่งข้อมูลเป้าหมายและดัชนีจากนั้นนำผลต่างนั้นไปใช้กับไดเรกทอรีการทำงานและดัชนี โดยทั่วไปหมายความว่าทำให้เนื้อหาของดัชนีตรงกับเนื้อหาของการแก้ไขที่คุณระบุ ความจริงที่ว่าgit diffใช้อาร์กิวเมนต์เส้นทางช่วยให้คุณสามารถ จำกัด ผลกระทบนี้ไปยังไฟล์หรือไดเรกทอรีที่เฉพาะเจาะจง

เนื่องจากคำสั่งนี้ค่อนข้างใช้เวลานานและฉันวางแผนที่จะใช้บ่อยฉันจึงตั้งชื่อแทนสำหรับชื่อที่ฉันใช้reset-checkout:

git config --global alias.reset-checkout '!f() { git diff --cached "$@" | git apply -R --index; }; f'

คุณสามารถใช้สิ่งนี้:

git reset-checkout 451a9a4 -- path/to/directory

หรือเพียงแค่:

git reset-checkout 451a9a4

ฉันเห็นความคิดเห็นของคุณเมื่อวานและทดลองในวันนี้ นามแฝงของคุณมีประโยชน์ +1
VonC

ตัวเลือกนี้เปรียบเทียบกับgit checkout --overlay HEAD -- <path>คำสั่งที่ @VonC กล่าวถึงในคำตอบของเขาอย่างไร
Ehtesh Choudhury

1
@EhteshChoudhury หมายเหตุที่git checkout --overlay HEAD -- <path>ยังไม่วางจำหน่าย (Git 2.22 จะออกในไตรมาส 2 ปี 2019)
VonC

5

ฉันจะเสนอตัวเลือกที่น่ากลัวที่นี่เนื่องจากฉันไม่รู้ว่าจะทำอะไรกับคอมไพล์ยกเว้นadd commitและpushไปนี้เป็นวิธีที่ฉัน "ย้อนกลับ" ไดเรกทอรีย่อย

ฉันเริ่มพื้นที่เก็บข้อมูลใหม่บนพีซีในพื้นที่ของฉันย้อนกลับสิ่งทั้งหมดเป็นการกระทำที่ฉันต้องการคัดลอกรหัสจากนั้นคัดลอกไฟล์เหล่านั้นไปยังไดเรกทอรีการทำงานของฉัน add commit pushและ voila อย่าเกลียดผู้เล่นเกลียดนาย Torvalds เพราะฉลาดกว่าพวกเราทุกคน


4

โดยปกติการรีเซ็ตจะเปลี่ยนทุกอย่าง แต่คุณสามารถgit stashเลือกสิ่งที่คุณต้องการเก็บไว้ได้ ดังที่คุณกล่าวถึงstashไม่ยอมรับเส้นทางโดยตรง แต่ก็ยังสามารถใช้เพื่อรักษาเส้นทางเฉพาะด้วยการ--keep-indexตั้งค่าสถานะ ในตัวอย่างของคุณคุณจะซ่อนไดเรกทอรี b จากนั้นรีเซ็ตทุกอย่างอื่น

# How to make files a/* reappear without changing b and without recreating a/c?
git add b               #add the directory you want to keep
git stash --keep-index  #stash anything that isn't added
git reset               #unstage the b directory
git stash drop          #clean up the stash (optional)

สิ่งนี้ทำให้คุณถึงจุดที่ส่วนสุดท้ายของสคริปต์ของคุณจะแสดงผลลัพธ์นี้:

After checkout:
# On branch master
# Changes not staged for commit:
#
#   modified:   b/a/ba
#
no changes added to commit (use "git add" and/or "git commit -a")
a/a/aa
a/b/ab
b/a/ba

ฉันเชื่อว่านี่เป็นผลลัพธ์เป้าหมาย (b ยังคงถูกแก้ไขไฟล์ / * กลับมาเป็น / a ไม่ได้ถูกสร้างขึ้นใหม่)

วิธีนี้มีประโยชน์เพิ่มเติมในการยืดหยุ่นมาก คุณสามารถปรับขนาดให้เล็กลงได้ตามที่คุณต้องการเพิ่มไฟล์เฉพาะ แต่ไม่ใช่ไฟล์อื่นในไดเรกทอรี


เยี่ยมมาก แต่ฉันต้องทำgit addทุกอย่างยกเว้นaใช่มั้ย ฟังดูยากในทางปฏิบัติ
krlmlr

1
@krlmlr ไม่จริง คุณสามารถgit add .แล้วจะเพิ่มทุกอย่างยกเว้นgit reset a a
Jonathan Wren

1
@krlmlr นอกจากนี้ยังเป็นที่น่าสังเกตว่าgit addจะไม่เพิ่มไฟล์ที่ถูกลบ ดังนั้นหากคุณกู้คืนไฟล์ที่ถูกลบเท่านั้นgit add .จะเพิ่มไฟล์ที่ถูกแก้ไขทั้งหมด แต่ไม่ใช่ไฟล์ที่ถูกลบ
Jonathan Wren

3

หากขนาดของไดเรกทอรีย่อยมีขนาดไม่ใหญ่มากและคุณต้องการอยู่ห่างจาก CLI ต่อไปนี้เป็นวิธีแก้ปัญหาอย่างรวดเร็วเพื่อรีเซ็ตไดเรกทอรีย่อยด้วยตนเอง :

  1. สลับไปที่สาขาหลักและคัดลอกไดเรกทอรีย่อยที่จะรีเซ็ต
  2. ตอนนี้สลับกลับไปที่สาขาคุณสมบัติของคุณและแทนที่ไดเรกทอรีย่อยด้วยสำเนาที่คุณเพิ่งสร้างในขั้นตอนที่ 1
  3. ยอมรับการเปลี่ยนแปลง

ไชโย คุณเพียงแค่รีเซ็ตไดเรกทอรีย่อยในสาขาฟีเจอร์ของคุณด้วยตนเองเหมือนของสาขาหลัก !!


ฉันไม่เห็นคะแนนใด ๆ สำหรับคำตอบนี้ แต่บางครั้งนี่เป็นเส้นทางสู่ความสำเร็จที่ง่ายที่สุด
Sue Spence

1

คำตอบของAjedi32คือสิ่งที่ฉันกำลังมองหา แต่สำหรับความมุ่งมั่นบางอย่างฉันพบข้อผิดพลาดนี้:

error: cannot apply binary patch to 'path/to/directory' without full index line

อาจเป็นเพราะไฟล์บางส่วนของไดเรกทอรีเป็นไฟล์ไบนารี การเพิ่มตัวเลือก '- ไบนารี' ไปยังคำสั่ง git diff แก้ไขมัน:

git diff --binary --cached commit -- path/to/directory | git apply -R --index

0

เกี่ยวกับอะไร

subdir=thesubdir
for fn in $(find $subdir); do
  git ls-files --error-unmatch $fn 2>/dev/null >/dev/null;
  if [ "$?" = "1" ]; then
    continue;
  fi
  echo "Restoring $fn";
  git show HEAD:$fn > $fn;
done 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.