question: so now we fixed if a branch is 100% included in the default branch (or another branch) but what if a branch is branched of the 10th commit (from HEAD) of main, how would the code handle this

question: if we would push to a backend that supports sha256 could we do the translation on the fly?
Short answer: technically yes, but not in the streaming-relay sense git-sync is built around — it becomes a full materialize-translate-repack path, and it breaks signatures.
Why it isn't cheap:
-
Object contents embed hashes. Trees list entry SHA-1s, commits list parent/tree SHA-1s, tags list target SHA-1s. To produce SHA-256 objects you must decode every object, rewrite internal references, and rehash. That kills the relay optimization in
bootstrap/sync— you can't just forward the source packfile bytes (internal/relay is the current fast path; seedocs/bootstrap.md). -
You need a persistent bidirectional mapping. For incremental runs, target tips are SHA-256 but the source's
havenegotiation expects SHA-1. git-sync would have to carry (or re-derive) a SHA-1↔SHA-256 map across runs, or re-translate everything each time. Git core handles this viaextensions.compatObjectFormat/ the "transition plan" loose-object mapping — doing it outside git means reimplementing that table. -
Signed commits and signed tags break. The signature covers the object bytes. Rewriting references changes the bytes, so every GPG/SSH signature becomes invalid. There's no fix-up; this is inherent to content-addressed storage.
-
Memory/cost profile changes. Current
--materialized-max-objectsguardrail already bounds the fallback in-memory path. A translating push is strictly a materialized path plus a rewrite pass and a mapping table, so it's more expensive than today's worst case, not less. -
Receive-pack on a SHA-256 target speaks SHA-256 wire format. That's a protocol switch too (the push path is still v1-style here; wire-level object IDs, ref advertisement, capabilities like
object-format=sha256all change). So the push side needs a second codepath, not a flag flip.
Practical take: it would be a separate --translate-to sha256 mode that replaces relay with decode-rewrite-rehash-repack, persists a hash map alongside managed refs, and documents that signatures don't survive. Worth it if the goal is a one-shot migration to a SHA-256 backend; not worth grafting onto the incremental relay path.
If you want, I can sketch where this would slot into the engine (plan → materialized translate stage → new push codec) without writing code yet.
different question: if the target supports SHA256 could it translate on receive? is that a thing?
Yes — this is actually a thing in git core, called the hash function transition / interop mode, using extensions.compatObjectFormat.
How it works server-side:
- The target repo is created with
objectFormat = sha256andcompatObjectFormat = sha1(or vice versa). Git then stores a bidirectional hash map for every object it receives. - On
receive-pack, if the client pushes SHA-1 wire format (negotiated via theobject-formatcapability), the server translates incoming objects into its primary format and records the mapping. Trees/commits are rewritten so internal references point at the primary-format hashes; the original form is recoverable through the map. - On
upload-pack/ref advertisement, the server can serve either format to a client depending on whichobject-format=the client negotiates. SHA-1 clients see SHA-1 hashes; SHA-256 clients see SHA-256. - Signed commits/tags: the transition plan handles this by signing in a canonical form and carrying the signature + original bytes; interop is designed so signatures still verify under the hash they were made in. (In practice this is the most brittle part and the bit operators hit issues with.)
The upshot for git-sync:
- If the target server actually implements compat mode, git-sync needs no translation code at all. You just push SHA-1 as today; the server does the work. git-sync's relay path even stays intact, because the wire bytes can flow through unchanged until they hit the server.
- You do need to negotiate
object-format=sha1in the push capabilities, and confirm the server advertisesobject-formatat all. Today git-sync'sreceive-packpath doesn't inspect that; it would want to, just to fail loudly if the target is SHA-256-only with no compat. - For incremental reruns, target ref advertisement comes back in whatever format the client asked for, so the
havelist stays SHA-1 and negotiation keeps working.
Current reality check (worth verifying against today's forges before relying on it):
- Git core has had this since ~2.42, marked experimental.
- GitLab has had some SHA-256 repo support; GitHub historically has not shipped SHA-256 repos.
- Gitea/Forgejo have varying support.
- Self-hosted git daemons are the most likely place you'd actually encounter a SHA-256 target with compat mode.
So: "translate on receive" is the right architectural layer for this problem, and it already exists in git — it's just a server capability question, not a git-sync feature request. The sensible thing for git-sync is to detect and surface it (probe object-format capabilities; refuse a SHA-256-only push target with a clear error) rather than try to implement translation itself.
Yes, later incremental sync works, as long as two conditions hold on the target:
-
Compat mode is permanent, not bootstrap-only. The SHA-1↔SHA-256 mapping has to stay enabled for the life of the repo, not just the initial import. Git does persist the map alongside objects, so this is the default once
compatObjectFormatis set — but if an operator ever disables it later, the SHA-1 view disappears and your next sync has nothing to negotiate against. -
git-sync always negotiates
object-format=sha1with the target. Both on theinfo/refs?service=git-receive-packadvertisement and on the push itself. That's what makes the server return ref tips as SHA-1 hashes (translated through the map) instead of its native SHA-256 form. Without this, target advertisement comes back as SHA-256 and is useless ashaveinput to a SHA-1 source.
Given those, the incremental flow stays identical to today:
- Probe target → get SHA-1 tips (via compat map).
- Advertise those as
haveto source upload-pack. - Source returns a thin SHA-1 pack of only new objects.
- Relay to target; server translates and extends the map.
- Fast-forward checks,
--prune,--force, tag immutability — all operate on SHA-1 hashes on both sides, so the existing logic is untouched.
Edge cases that still behave correctly:
- Third-party pushes to the target in SHA-256 between syncs. The server computes compat SHA-1s for those objects too, so advertisement stays consistent. git-sync will see a SHA-1 tip it doesn't recognize; source won't have it as a
havematch, and the next fetch just won't benefit from it. No corruption, just a slightly less efficient negotiation. - Ref rewrites on target. Hash comparison still works because both sides of the comparison are SHA-1.
- Tag retargeting. Same story —
--forcesemantics are hash-based, format-agnostic.
One thing that does not work, and is worth flagging loudly: if compat mode was never enabled and the target is SHA-256-only, the first sync fails at capability negotiation, and no amount of later retrying fixes it without reconfiguring the target repo. So a sensible git-sync behavior would be to detect object-format=sha256 without object-format=sha1 in the receive capabilities on the very first probe and error out with a clear "target is SHA-256 only, enable compatObjectFormat=sha1 or this tool can't sync from a SHA-1 source" message — rather than discovering it mid-push.
on the tool: The tool would easily support moving only branches around, right? like for example we could setup that main comes always from source but we push feature branches from target to source
Yes, with one nuance: it's two invocations, not one config. The tool is one-directional per run by design — source → target with explicit scope — so you orchestrate the split externally:
The scope flags already cover what you need per run:
--branchfilters which refs that invocation touches (cmd/git-sync/main.go:73).--map src:dstlets you rename during relay if the two sides use different conventions.--pruneis per-invocation, so each direction only prunes refs it manages — the two runs don't step on each other as long as their--branchsets are disjoint.- Fast-forward safety,
--force, replicate mode, all behave the same regardless of which side you call "source."
The sharp edges to know about:
-
No wildcard branches. README is explicit: "Ref mapping is explicit, not wildcard-based."
feature/*means enumerating branches, or listing them from aprobeand piping into--branch. If bidirectional mixed-ownership becomes a common workflow, a--branch-pattern 'feature/*'flag on top of the existing planner filter is the obvious addition — small change, fits the existing scope model. -
Ownership discipline is on you. Nothing in the tool prevents you from running both directions over overlapping branch sets and getting a ping-pong. The guardrail is your
--branchlist on each invocation. A lightweight config file that declares "these refs, this direction" and dispatches two runs would be a thin wrapper, not a core change — probably belongs in the caller (cron, CI, a small driver script), not in git-sync itself, to keep the CLI's semantics narrow. -
replicateis directional too. Ifmainshould be strictly source-authoritative (overwrite target even on divergence), usereplicatefor that run; keep the feature-branch run as plainsyncwith fast-forward safety so target→source pushes from devs don't silently overwrite anything on the source side. -
Auth is per-run. Both directions need credentials with push rights on their respective targets;
GITSYNC_SOURCE_TOKEN/GITSYNC_TARGET_TOKENget swapped between the two invocations. Trivial, but worth calling out if you're wiring this into CI secrets.
So: supported today for explicit branch lists; wants a glob flag if you want to express "all feature/*" declaratively; fundamentally the right shape because the split-ownership policy lives in the operator's config, not inside the sync engine.
time=2026-04-15T13:45:04.173+02:00 level=INFO msg="bootstrap batch planning checkpoints" branch_ref_count=152 planner=graph time=2026-04-15T13:45:04.173+02:00 level=INFO msg="bootstrap batch fetching commit graph" branch=refs/heads/20260116-git-hook-absolute-path time=2026-04-15T13:45:04.358+02:00 level=INFO msg="bootstrap batch planned checkpoints" branch=refs/heads/20260116-git-hook-absolute-path chain_len=40 estimated_batches=1 time=2026-04-15T13:45:04.358+02:00 level=INFO msg="bootstrap batch fetching commit graph" branch=refs/heads/20260119-do-not-pin-go time=2026-04-15T13:45:04.516+02:00 level=INFO msg="bootstrap batch planned checkpoints" branch=refs/heads/20260119-do-not-pin-go chain_len=41 estimated_batches=1 time=2026-04-15T13:45:04.516+02:00 level=INFO msg="bootstrap batch fetching commit graph" branch=refs/heads/20260128-goreleaser-does-not-create-GH-releases time=2026-04-15T13:45:04.826+02:00 level=INFO msg="bootstrap batch planned checkpoints" branch=refs/heads/20260128-goreleaser-does-not-create-GH-releases chain_len=82 estimated_batches=1 time=2026-04-15T13:45:04.826+02:00 level=INFO msg="bootstrap batch fetching commit graph" branch=refs/heads/20260206-farting-around-with-secrets time=2026-04-15T13:45:05.210+02:00 level=INFO msg="bootstrap batch planned checkpoints" branch=refs/heads/20260206-farting-around-with-secrets chain_len=128 estimated_batches=1
I guess one optimization could be that we try to find the default branch first?
Yes — and there's actually a bigger win hiding behind that suggestion. Ordering alone helps, but the real payoff is using the trunk's reachable set as a cutoff for every other branch's graph walk.
What the log is showing. planCheckpointsFromChain at internal/strategy/bootstrap/bootstrap.go:567 is called per branch in a plain loop (bootstrap.go:491). Each branch independently:
- Fetches the full commit graph back to root (
FetchCommitGraph, tree:0, no cutoff). - Walks first-parent to get a chain.
- Plans checkpoints on that chain.
Nothing shares work across branches. For 152 feature branches that almost all descend from main, you're refetching and rewalking ~99% of main's history 152 times.
Tier 1 — cheap: default-branch-first ordering.
- Read
HEADsymref from the source advertisement. Protocol v2ls-refsreturns it with thesymrefsargument; v1info/refsincludes it in capabilities. git-sync already talks to the source at probe time, so no extra round-trip. - Sort
planRefsin bootstrap.go:465 so HEAD's branch is first; everything else stays in current order. - Benefit at push time: the target's object store has trunk objects when feature-branch batches start, which means
receive-packhandles subsequent batches as thin packs against a larger reachable set. This is free and small.
Tier 2 — the real win: trunk-reachability cutoff.
After planning trunk, you know every commit hash reachable from trunk's tip (you already compute the first-parent chain at bootstrap.go:581, and FetchCommitGraph materialized the full ancestor set). For every subsequent branch:
- Pass trunk tip as a
havetoFetchCommitGraph. Protocol v2fetchhonors haves; the source only sends commits not reachable from trunk. A "main + 5 commits" feature branch returns 5 commits instead of 75k. - Short-circuit
FirstParentChainwhen it hits a commit already in trunk's reachable set — no need to walk past the fork point. chain_len becomes divergence length, not absolute length.
Rough impact on your log output: today each branch takes ~150–400ms and chain_len is 40–128. Post-optimization, trunk is one ~normal planning pass, and each subsequent branch is probably ~20–50ms with chain_len in the single digits for the typical "branched off main last week" case. For 152 branches that's the difference between ~40s and ~5s on the planning phase alone, and the actual relay gets proportionally cheaper too because non-trunk batches shrink to their divergence deltas.
Things to handle cleanly.
- No clear trunk. If
HEADisn't advertised, or--branch/--mapexcludes it, fall back to current behavior. Alternatively pick the pivot as "branch with the longest graph" by doing one probe-time graph walk; probably not worth it — HEAD is the right heuristic 99% of the time. - Branches with disjoint roots. The have-limited fetch just returns their full history; degrades gracefully.
- Shallow planner (bootstrap.go:602) already does incremental depth doubling per-branch; the same cross-branch reuse would apply there too — remember trunk's ancestry from the first full walk and stop shallow-depth doubling on other branches once they land inside it.
- Cache lifecycle. Trunk's reachable-set is the big thing to keep across branches. The current code explicitly nils
graphStoreandruntime.GC()s after extracting the chain (bootstrap.go:582) specifically because the full store is huge (4.6 GB for linux per the comment). You don't want to keep the full store around for 152 branches. But you only need the hash set of trunk's ancestry for cutoff purposes — that's 8-ish bytes per commit × ~75k commits = ~600 KB. Extract that set before you drop the store, carry it forward, drop it after planning finishes.
Where in the code it goes. The minimal version is: lift ordering into planBatches (bootstrap.go:489), compute trunk's reachability set on the first iteration, thread it as a cutoff into subsequent FetchCommitGraph calls and into FirstParentChain. That's two signature changes (FetchCommitGraph gains optional haves; FirstParentChain gains an optional "stop-at" set) and some wiring in the loop. No new concepts, just sharing what's already computed.
Worth a design note in docs/bootstrap-batching.md before implementing, since it changes the per-branch invariants the current code relies on. Want me to sketch that note?
Good — only bootstrap uses commit-graph fetches, so the single plumbing change is enough.
Quick status:
Implemented both tiers:
- Tier 1 — default-branch-first:
orderTrunkFirstin bootstrap.go uses the newSourceHeadTargetparam (derived from the v2symrefsresponse or v1symref=capability) to sort the trunk first; the rest of the order is preserved. - Tier 2 — trunk-reachability cutoff:
planCheckpointsFromChainnow takestrunkHaves []plumbing.Hash(passed as haves toFetchCommitGraph, so the source skips commits reachable from trunk) andtrunkStopAt map[plumbing.Hash]struct{}(passed to the newFirstParentChainStoppingAt, so the walk terminates at trunk ancestors). The trunk's full commit set is extracted from the graph store viacollectCommitHashesbefore the store is GC'd — ~8 B × commit count, so ~11 MB for linux vs the ~4.6 GB store.
Files touched:
internal/gitproto/refs.go— HEAD symref extraction (v1 viacapability.SymRef, v2 viasymrefsls-refs arg); newRefService.HeadTargetfield.internal/gitproto/fetch.go—FetchCommitGraphtakes a haves slice.internal/planner/checkpoint.go— addedFirstParentChainStoppingAt, keptFirstParentChainas a thin wrapper.internal/strategy/bootstrap/bootstrap.go— newSourceHeadTargetparam,orderTrunkFirst, trunk cutoff wiring, empty-chain fallback for subsumed branches.internal/syncer/syncer.go— plumbss.sourceService.HeadTargetintobstrap.Params.- Tests:
internal/planner/planner_test.go,internal/gitproto/refs_test.go,internal/gitproto/fetch_test.go,internal/strategy/bootstrap/bootstrap_test.go— new unit tests for stop-set walk,orderTrunkFirstordering edge cases (HEAD filtered out, no HEAD hint, non-zero trunk index), v1 HEAD symref parsing, and v2 ls-refs symref-target attribute parsing.
Behavior when HEAD is unknown or filtered out by --branch: falls back to existing per-branch behavior; no regression.
go build ./... and go test ./... both green. The gated e2e integration tests (GITSYNC_E2E_GIT_HTTP_BACKEND=1, GITSYNC_E2E_LIVE_LINUX=1) will exercise the new path end-to-end when run.
Want me to add a note to docs/bootstrap-batching.md describing this change before committing?
❯ time go run ./cmd/git-sync replicate -v
--target-max-pack-bytes 2000000000
--measure-memory --stats --json
$SRC $TGT 2>&1 | tee /tmp/linux-replicate-11.log
time=2026-04-15T14:31:44.639+02:00 level=INFO msg="bootstrap batch planning checkpoints" branch_ref_count=152
time=2026-04-15T14:31:44.639+02:00 level=INFO msg="bootstrap batch fetching commit graph" branch=refs/heads/20260116-git-hook-absolute-path have_count=0 stop_at_count=0
time=2026-04-15T14:31:44.832+02:00 level=INFO msg="bootstrap batch planned checkpoints" branch=refs/heads/20260116-git-hook-absolute-path chain_len=40 estimated_batches=1
time=2026-04-15T14:31:44.832+02:00 level=INFO msg="bootstrap batch fetching commit graph" branch=refs/heads/20260119-do-not-pin-go have_count=0 stop_at_count=0
time=2026-04-15T14:31:45.011+02:00 level=INFO msg="bootstrap batch planned checkpoints" branch=refs/heads/20260119-do-not-pin-go chain_len=41 estimated_batches=1
Do we log anything when head couldn't be found?
All green. Summary:
Fix 1 — the silent bug. listSourceRefsV2 now always adds ref-prefix HEAD to the ls-refs args, and decodeV2LSRefs extracts the symref target from HEAD and then drops HEAD from the returned ref slice (matches v1 behavior via the symref capability). Previously, because planner.RefPrefixes always passes refs/heads/, HEAD was filtered out of the v2 response entirely and HeadTarget was always empty on v2 sources — exactly what your log showed.
Fix 2 — observability. planBatches now emits one of three log lines up front:
bootstrap batch trunk unset reason="source did not advertise a HEAD symref"— source gave us nothing (rare but possible on minimal git servers).bootstrap batch trunk unset reason="HEAD ref not in desired set (filtered by --branch or --map)" source_head_target=refs/heads/main— source advertised HEAD but the user's scope excluded it.bootstrap batch trunk selected source_head_target=refs/heads/main trunk_target_ref=refs/heads/main— fast path active; the first branch in the checkpoint logs after this should showhave_count=1 stop_at_count=<trunk_ancestry>.
Re-run your command and you should now see the trunk-selected line, then the first branch planning with have_count=0 stop_at_count=0 (trunk itself), then subsequent branches with have_count=1 stop_at_count=<big number> and drastically shorter chain_len values.
the old run did cause a lot of webhook triggers from github, which I guess is expected given we pushed each branch individually. Should this still be the case with the new approach?
Short answer: yes, expect roughly the same webhook volume — the optimization I implemented is entirely on the planning side, not the push side, and GitHub fires one webhook event per ref update regardless of how those pushes are batched.
Here's the precise picture:
What the trunk-first change reduces
- Source-side commit-graph fetches (smaller, faster, fewer objects).
FirstParentChainwalk length per branch.- Objects actually transferred in later batch packs (because have-negotiation via completed branch tips, already in place since 3c20da5).
What it does not reduce
- Number of
receive-packinvocations against the target. - Number of ref updates.
- Number of webhook deliveries from GitHub.
The batched bootstrap's push pattern is still: for each branch, push its checkpoint pack(s) to refs/gitsync/bootstrap/heads/<branch>, then cut over to the real refs/heads/<branch>. With 152 branches that's still ~152 temp-ref updates plus ~152 cutover updates, and GitHub generates a push event per ref-update, not per HTTP call.
Three levers you could actually pull to cut webhook volume
-
Disable the webhook on the target during bootstrap. By far the easiest operationally: most teams do exactly this for migrations and large one-shot imports. Re-enable after the final cutover batch. No code changes needed.
-
Skip the pack push for fully-subsumed branches. My trunk-first change already produces
chain=[tip]for branches whose tip lives inside trunk's ancestry. Today that still does a tiny fetch + push. We could detect this case (branch tip is in the trunk reachable set) and emit a single ref-creation command with no pack at all — that's still a webhook, but fewer HTTP calls and no spurious "0 objects" pushes. Small win. -
Batch cutover ref updates into a single
receive-packcall.receive-packaccepts N ref-update commands in one push. Doing one HTTP call with 152 cutover commands instead of 152 separate calls would cut HTTP overhead a lot — but GitHub still fires 152 push events, one per ref. So this helps throughput, not webhook noise.
If webhooks are the real pain point, option 1 is the right answer; the tooling can't do better than git's per-ref event model. If you want me to implement option 2 (skip-pack for subsumed branches) or add a --disable-target-webhooks helper that uses the GitHub API to toggle them around the run, say the word — both are reasonable follow-ups but not part of the current planning optimization.
No — we already have exactly what we need, and it's the same ~11 MB hash set the trunk-first optimization already keeps in memory.
Look at the current flow in bootstrap.go:
- Fetch trunk's commit graph into
graphStore(~4.6 GB transient for linux). collectCommitHashes(graphStore)— extracts just the hashes intoancestors(~8 B × commit count ≈ 11 MB for linux).- Drop
graphStore, GC reclaims the 4.6 GB. ancestorsbecomestrunkStopSet, carried across the per-branch loop.
trunkStopSet already contains every commit reachable from the trunk tip (not just the first-parent chain — collectCommitHashes iterates all CommitObjects in the store, and the tree:0 fetch returns the full reachability). So "is this branch tip fully subsumed by trunk?" is a one-line O(1) check against a set we already have:
That's it. No additional memory. In fact it makes things cheaper than the current code path, because today when a branch is subsumed my code still fetches its commit graph (which returns near-nothing via the have-negotiation but still round-trips) and does a tiny push. Option 2 would skip both.
The only genuine caveat — not a memory one — is correctness edge cases:
- Branch tip == trunk tip: trivially in the set, skip is safe. Webhook fires for the ref creation, but no pack overhead.
- Branch is an older commit on trunk's first-parent line: in the set, skip is safe.
- Branch is reachable only via a non-first-parent merge on trunk: still in the set (because
collectCommitHashespulls every commit the tree:0 fetch returned, including second parents and their ancestors), so skip is still safe. - Branch tip not reachable from trunk at all: not in the set, falls through to the current planning path. Correct.
So the question is really about code surface, not memory. Want me to implement it as a follow-up to the current change?
Done. Summary of what option 2 adds:
Planning-side (planBatches): before each non-trunk branch's commit-graph fetch, check trunkStopSet[ref.SourceHash]. On hit, emit a plannedBatch{subsumed: true} with just a BranchPlan (no TempRef, no Checkpoints, no chain). Log line: bootstrap batch branch subsumed by trunk branch=<ref> source_hash=<sha>.
Execution-side (executeBatched): at the top of the batch loop, branch on batch.subsumed. On hit: single PushCommands call with one ref-create at SourceHash, update completedRefs, increment BatchCount, log bootstrap batch subsumed branch finalized. No temp ref, no pack fetch, no pack push, no cutover-then-delete dance.
Cost saved per subsumed branch: one commit-graph fetch + one pack fetch + one pack push + one temp-ref-update + one cutover push + one temp-ref delete → replaced by a single ref-create command. On webhooks you still get one event for the ref creation; everything else is pure network/CPU savings.
Correctness: trunkStopSet is built from every CommitObject in trunk's fetched graph (not just first-parent), so any branch tip that's reachable through trunk's merge parents is also detected as subsumed. Non-subsumed branches flow through the unchanged original path. If there's no trunk (HEAD unavailable or filtered out), trunkStopSet is nil and the subsumed check is skipped entirely — no regression.
Test TestExecuteBatchedSubsumedBranchSkipsPack in bootstrap_test.go verifies: fetchCommitGraph is called once (trunk only), fetchPack is called once (trunk only), PushPack is called once (trunk only), and the feature branch's ref is created by a single PushCommands with Old=0, New=featureHash, Delete=false.
All tests green.
All green. Two meaningful commits on top of b0f3048:
-
c95cea3 — Plan trunk first in batched bootstrap to cut per-branch graph fetches
- HEAD symref discovery (v1 via
capability.SymRef, v2 viasymrefs+ theref-prefix HEADfix) FetchCommitGraphhaves paramFirstParentChainStoppingAtwalk primitiveorderTrunkFirst,collectCommitHashes,SourceHeadTargetplumbing, trunk-selected/unset diagnostic logs- 389 insertions / 48 deletions across 9 files
- HEAD symref discovery (v1 via
-
411c469 — Skip pack push for branches fully subsumed by trunk
subsumedflag onplannedBatch- Planning-side detection before the commit-graph fetch
- Execution-side single-ref-create handling
- Webhook-volume caveat in the commit message since that's the operational question you asked
- 126 insertions across 2 files
Note: a pre-commit hook moved HEAD onto a new branch soph/branch-optimizations on the first commit. Your starting branch soph/plan-optimization is untouched. If you want these on the original branch instead, say the word and I'll reset and re-apply.