A force-with-lease védelmét egy háttérben futó fetch kijátszhatja

A force-with-lease opció nem feltétlenül állítja meg más commitjának felülírását: egy IDE háttérben futó fetch-e észrevétlenül érvényesítheti a lease-t. Itt a reprodukció és a valóban biztonságos Git-parancs.

Ha rebase után automatikusan a git push --force-with-lease után nyúlsz, valószínűleg úgy gondolsz rá, mint a force push biztonsági övére. A parancs tényleg megállít egy idegen commit felülírása előtt – egészen addig, amíg az editorod a háttérben nem futtat egy git fetch-et. Onnantól ugyanaz a parancs zöld utat adhat pontosan annak a felülírásnak, amitől védene.

Git commitgráf, amelyen egy háttérben futó fetch frissíti a követő refet, majd a force-with-lease felülír egy távoli commitot

Nem azt védi, amit gondolsz

A --force-with-lease dokumentációja szerint a rövid forma nem azt kérdezi a szervertől, hogy „változott-e a branch, mióta utoljára láttam?”. A Git nem tudja, mikor nézted meg vagy hagytad jóvá a távoli állapotot. Ehelyett azt ellenőrzi, hogy a szerveren lévő ref pillanatnyi értéke megegyezik-e a helyi remote-tracking ref – például az origin/feature – értékével.

Tegyük fel, hogy a távoli feature és a helyi origin/feature egyaránt az A commitra mutat. Egy kolléga feltolja a B commitot, de te még nem fetcheltél. A szerver B-t, a géped továbbra is A-t vár: a lease érvénytelen, a push stale info hibával megáll. Eddig pontosan azt kapjuk, amit vártunk.

A csavar az, hogy a git fetch frissíti a remote-tracking brancheket, miközben sem a working tree-t, sem a lokális feature branchet nem módosítja. Ha ezt egy IDE, Git GUI vagy időzített feladat végzi el, az origin/feature csendben B-re ugrik. A következő lease már B-t vár, a szerver szintén B-n áll, tehát a force push engedélyt kap – akkor is, ha B tartalmát soha nem nézted meg és nem építetted be.

Egy fetch, és eltűnik a riasztás

Ezt két klónnal és egy lokális bare remote-tal próbáltam ki. Az egyik klón feltolt egy új commitot, a másik pedig egy eltérő történetet próbált force pusholni:

# origin/feature még a korábban látott commitra mutat
git push --force-with-lease origin feature
# elutasítva: stale info

# ugyanaz, amit egy háttérben frissítő IDE is megtehet
git fetch origin

git push --force-with-lease origin feature
# sikeres forced update

Az első push 1-es kilépési kóddal leállt. A fetch után ugyanaz a push 0-val lefutott, és a kolléga commitja kikerült a távoli branch történetéből. Ez nem Git-bug. A rövid lease csak a remote-tracking refet tudja heurisztikaként használni arra, hogy melyik távoli állapotot láthattad.

A leginkább „na, ezt nem tudtam” rész a kézikönyvben egy mondattal lejjebb van: az explicit elvárt értéket nem megadó --force-with-lease formákat a Git dokumentációja jelenleg is kísérletinek nevezi. Ez elég erős jelzés ahhoz, hogy ne kezeljük a rövid kapcsolót feltétel nélküli adatvesztés-védelemként.

A kevésbé kényelmes, de valódi lease

A robusztus megoldás az, hogy a művelet elején elmented annak a commitnak az azonosítóját, amelyet ténylegesen ellenőriztél, majd ezt adod át elvárt értékként:

git fetch origin
expected=$(git rev-parse origin/feature)

# Ellenőrzés, majd például az utolsó commit átírása
git log --oneline HEAD..origin/feature
git commit --amend --no-edit

git push \
  --force-with-lease=refs/heads/feature:$expected \
  origin HEAD:feature

A shell-változó nem mozdul el egy későbbi háttér-fetch hatására. A push csak akkor írja át a feature branchet, ha az még mindig pontosan az elmentett commitra mutat. Ha közben bárki pusholt, a művelet leáll. Hosszabb munkánál ugyanezt egy ideiglenes lokális taggel is rögzítheted; a Git kézikönyve is ezt a mintát mutatja.

Van egy köztes opció is: a --force-if-includes a rövid lease mellé téve ellenőrzi, hogy a frissült remote-tracking csúcs bekerült-e a lokális branch refloggal leírt történetébe. A fenti háttér-fetches tesztben ez már visszautasította a push-t:

git push --force-with-lease --force-if-includes origin feature

Hasznos plusz korlát, de nem ugyanazt fejezi ki, mint egy konkrét, általad jóváhagyott commit azonosítója. Explicit <ref>:<expect> lease mellett ráadásul a kapcsoló nem csinál semmit.

Mit használnék productionben?

Saját, rövid életű feature branchen vállalható a history rewrite, de az explicit commitos lease-et használnám. Ha a csapatnak túl körülményes a hosszú parancs, a --force-if-includes jó második háló, nem tökéletes helyettesítő.

main, release branch vagy több ember által használt branch esetén nem kliensoldali fegyelemre építenék. A szerveren tiltanám a force push-t. A GitHub protected branch szabályai például alapértelmezésben blokkolják ezt, és státuszellenőrzéseket vagy review-t is megkövetelhetnek. A szerveroldali szabályt sem egy IDE, sem egy alias, sem egy rossz pillanatban lefutó fetch nem tudja véletlenül kijátszani.

A --force-with-lease továbbra is lényegesen jobb a vak --force-nál. Csak a neve sugall erősebb garanciát annál, amit a rövid forma ténylegesen ad. A lease akkor valódi lease, ha azt is megmondod, melyik commitra vetted fel.

Források

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük