{"course":{"slug":"building-apps-with-ai","title":"Building Apps with AI","url":"https://addaly.com/learn/building-apps-with-ai","level":"beginner","track":"build"},"lesson":{"slug":"merge-conflicts-without-panic","title":"Merge conflicts without panic","url":"https://addaly.com/learn/building-apps-with-ai/merge-conflicts-without-panic","position":42,"of":87,"minutes":7,"answer":"Git only stops where two sides changed the same lines; the merges that hurt are the clean ones, so build after every merge.","keyIdea":"Git only stops where two sides changed the same lines; the merges that hurt are the clean ones, so build after every merge."},"body":{"format":"markdown","text":"## What a conflict actually is\n\nGit merges by region. If one branch changed the top of a file and the other changed the bottom, both changes go in and nobody is asked. Git stops only when both sides changed the same lines differently, because there is no rule that can decide which version you meant.\n\nThat is the whole of it. A conflict is not damage. It is git declining to guess.\n\n## Reading the markers\n\nThe file is left on disk with both versions inside it:\n\n```text\n<<<<<<< HEAD\n  const fee = total * 0.05;\n=======\n  const fee = Math.round(total * 0.05 * 100) / 100;\n>>>>>>> payments\n```\n\nAbove the `=======` is the version on the branch you are on. Below it is the version from the branch you are merging in. The labels tell you which is which. Everything outside the markers has already been merged and needs no attention.\n\n## Resolving one\n\nOpen the file, decide (keep the top, keep the bottom, keep both, or write a third thing that combines them) and delete the three marker lines. Then:\n\n```bash\ngit add src/checkout.ts\ngit commit\n```\n\nEditors show buttons above each conflict: accept current, accept incoming, accept both. They do the same edit. `git status` lists every file still unresolved, so you can work through them one by one.\n\nIf you want out, `git merge --abort` returns the branch to exactly how it was before you started. Nothing is lost by trying a merge.\n\n## Conflicts that are not about your code\n\n`package-lock.json` conflicts constantly, because two branches that each installed a package both rewrote it. Do not resolve it by hand. Take either side and regenerate:\n\n```bash\ngit checkout --theirs package-lock.json   # or --ours\nnpm install\ngit add package-lock.json\n```\n\nGenerated files (build output, compiled CSS, snapshot files) get the same treatment. Regenerate rather than merge.\n\nMigrations are the one generated-looking file that is not. Two branches that each added a migration numbered `0007` will merge cleanly, because they are different files, and then fail when the migration tool finds two sevens. Renumber one and check that it still applies after the other.\n\n## The agent and the conflict\n\nAsk an agent to \"fix the conflicts\" and it will often resolve every one of them by taking one side wholesale. To a model reading the file, the markers are noise around code, and the quickest route to a file that parses is to keep one version. The other branch's work vanishes without an error.\n\nIf you use an agent here at all, ask it to explain what each side of each conflict does before touching anything, and resolve the decision yourself. Conflicts are the moment when two intentions met, and only you hold both.\n\n## Avoiding most of them\n\nSmall branches, merged soon. Pull before you start. Do not reformat files you are not otherwise changing: formatting churn is the largest single source of conflicts, because it touches every line and collides with any real edit to the same file. Put the formatter in a pre-commit hook so everything is formatted the same way before it is ever committed, and this category nearly disappears.\n\n## The conflict nobody sees\n\nA clean merge is not a correct merge. Two branches can each build perfectly and produce something that does not build together: one renamed a function, the other added a call to the old name in a different part of the file. The lines never overlapped, git had nothing to say, and the failure appears only when you run it.\n\nGit tracks text. It knows nothing about functions, imports or types. So the merge is not finished when the commit exists; it is finished when `npm run build` passes and the two-minute check from the reading block has been done on the merged result. Treat every merge as a change, because it is one."},"check":{"question":"A merge completes with no conflicts reported. The app then fails to build with 'cancelBooking is not defined'. What most likely happened?","options":[{"text":"Git corrupted the merge while combining the two histories, and the affected files need restoring from the remote copy before the build will pass.","correct":false,"tempting":"Git merges text deterministically; a result that builds wrongly is a correct merge of two incompatible changes, not a corrupt one."},{"text":"The lock file conflicted silently and dropped a package.","correct":false,"tempting":"Lock file conflicts are reported like any other; a missing function is a code problem, not a dependency one."},{"text":"One branch renamed the function and the other added a call to the old name in a different region, so git merged both cleanly.","correct":true},{"text":"The resolution was never staged, so the merge commit is incomplete.","correct":false,"tempting":"There was nothing to resolve; an unstaged resolution stops the commit rather than producing a broken one."}]},"prev":{"slug":"github-and-the-remote","title":"GitHub, and what a remote is for","url":"https://addaly.com/learn/building-apps-with-ai/github-and-the-remote"},"next":{"slug":"letting-the-agent-use-git","title":"Letting the agent use git, within limits","url":"https://addaly.com/learn/building-apps-with-ai/letting-the-agent-use-git"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Merge conflicts without panic', in Building Apps with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-apps-with-ai/merge-conflicts-without-panic (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Merge conflicts without panic","courseTitle":"Building Apps with AI","module":{"index":5,"slug":"save-points-and-shipping","title":"Save points, and shipping to a stranger"},"answer":null,"keyIdea":"Git only stops where two sides changed the same lines; the merges that hurt are the clean ones, so build after every merge.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"what-a-conflict-actually-is","runs":[{"text":"What a conflict actually is"}]},{"t":"p","runs":[{"text":"Git merges by region. If one branch changed the top of a file and the other changed the bottom, both changes go in and nobody is asked. Git stops only when both sides changed the same lines differently, because there is no rule that can decide which version you meant."}]},{"t":"p","runs":[{"text":"That is the whole of it. A conflict is not damage. It is git declining to guess."}]},{"t":"h2","id":"reading-the-markers","runs":[{"text":"Reading the markers"}]},{"t":"p","runs":[{"text":"The file is left on disk with both versions inside it:"}]},{"t":"code","lang":"text","label":"Text","text":"<<<<<<< HEAD\n  const fee = total * 0.05;\n=======\n  const fee = Math.round(total * 0.05 * 100) / 100;\n>>>>>>> payments"},{"t":"p","runs":[{"text":"Above the "},{"text":"=======","code":true},{"text":" is the version on the branch you are on. Below it is the version from the branch you are merging in. The labels tell you which is which. Everything outside the markers has already been merged and needs no attention."}]},{"t":"h2","id":"resolving-one","runs":[{"text":"Resolving one"}]},{"t":"p","runs":[{"text":"Open the file, decide (keep the top, keep the bottom, keep both, or write a third thing that combines them) and delete the three marker lines. Then:"}]},{"t":"code","lang":"bash","label":"Bash","text":"git add src/checkout.ts\ngit commit"},{"t":"p","runs":[{"text":"Editors show buttons above each conflict: accept current, accept incoming, accept both. They do the same edit. "},{"text":"git status","code":true},{"text":" lists every file still unresolved, so you can work through them one by one."}]},{"t":"p","runs":[{"text":"If you want out, "},{"text":"git merge --abort","code":true},{"text":" returns the branch to exactly how it was before you started. Nothing is lost by trying a merge."}]},{"t":"h2","id":"conflicts-that-are-not-about-your-code","runs":[{"text":"Conflicts that are not about your code"}]},{"t":"p","runs":[{"text":"package-lock.json","code":true},{"text":" conflicts constantly, because two branches that each installed a package both rewrote it. Do not resolve it by hand. Take either side and regenerate:"}]},{"t":"code","lang":"bash","label":"Bash","text":"git checkout --theirs package-lock.json   # or --ours\nnpm install\ngit add package-lock.json"},{"t":"p","runs":[{"text":"Generated files (build output, compiled CSS, snapshot files) get the same treatment. Regenerate rather than merge."}]},{"t":"p","runs":[{"text":"Migrations are the one generated-looking file that is not. Two branches that each added a migration numbered "},{"text":"0007","code":true},{"text":" will merge cleanly, because they are different files, and then fail when the migration tool finds two sevens. Renumber one and check that it still applies after the other."}]},{"t":"h2","id":"the-agent-and-the-conflict","runs":[{"text":"The agent and the conflict"}]},{"t":"p","runs":[{"text":"Ask an agent to “fix the conflicts” and it will often resolve every one of them by taking one side wholesale. To a model reading the file, the markers are noise around code, and the quickest route to a file that parses is to keep one version. The other branch’s work vanishes without an error."}]},{"t":"p","runs":[{"text":"If you use an agent here at all, ask it to explain what each side of each conflict does before touching anything, and resolve the decision yourself. Conflicts are the moment when two intentions met, and only you hold both."}]},{"t":"h2","id":"avoiding-most-of-them","runs":[{"text":"Avoiding most of them"}]},{"t":"p","runs":[{"text":"Small branches, merged soon. Pull before you start. Do not reformat files you are not otherwise changing: formatting churn is the largest single source of conflicts, because it touches every line and collides with any real edit to the same file. Put the formatter in a pre-commit hook so everything is formatted the same way before it is ever committed, and this category nearly disappears."}]},{"t":"h2","id":"the-conflict-nobody-sees","runs":[{"text":"The conflict nobody sees"}]},{"t":"p","runs":[{"text":"A clean merge is not a correct merge. Two branches can each build perfectly and produce something that does not build together: one renamed a function, the other added a call to the old name in a different part of the file. The lines never overlapped, git had nothing to say, and the failure appears only when you run it."}]},{"t":"p","runs":[{"text":"Git tracks text. It knows nothing about functions, imports or types. So the merge is not finished when the commit exists; it is finished when "},{"text":"npm run build","code":true},{"text":" passes and the two-minute check from the reading block has been done on the merged result. Treat every merge as a change, because it is one."}]}],"check":{"question":[{"text":"A merge completes with no conflicts reported. The app then fails to build with ‘cancelBooking is not defined’. What most likely happened?"}],"options":[{"text":[{"text":"Git corrupted the merge while combining the two histories, and the affected files need restoring from the remote copy before the build will pass."}],"correct":false,"tempting":[{"text":"Git merges text deterministically; a result that builds wrongly is a correct merge of two incompatible changes, not a corrupt one."}]},{"text":[{"text":"The lock file conflicted silently and dropped a package."}],"correct":false,"tempting":[{"text":"Lock file conflicts are reported like any other; a missing function is a code problem, not a dependency one."}]},{"text":[{"text":"One branch renamed the function and the other added a call to the old name in a different region, so git merged both cleanly."}],"correct":true,"tempting":null},{"text":[{"text":"The resolution was never staged, so the merge commit is incomplete."}],"correct":false,"tempting":[{"text":"There was nothing to resolve; an unstaged resolution stops the commit rather than producing a broken one."}]}],"order":[1,2,3,0],"why":null},"prev":{"slug":"github-and-the-remote","title":"GitHub, and what a remote is for","minutes":9},"next":{"slug":"letting-the-agent-use-git","title":"Letting the agent use git, within limits","minutes":8}}}