{"course":{"slug":"how-software-changed","title":"How Software Development Changed","url":"https://addaly.com/learn/how-software-changed","level":"intermediate","track":"work"},"lesson":{"slug":"git-as-a-safety-net","title":"Git is what makes speed safe","url":"https://addaly.com/learn/how-software-changed/git-as-a-safety-net","position":41,"of":87,"minutes":9,"answer":"Small reversible steps are only cheap if reversal is instant, so commit constantly, stage with git add -p, keep a clean tree before letting any tool edit files.","keyIdea":"Small reversible steps are only cheap if reversal is instant, so commit constantly, stage with `git add -p`, keep a clean tree before letting any tool edit files, and remember that reflog holds almost everything you think you destroyed."},"body":{"format":"markdown","text":"## Fast generation is only safe when reversal is instant\n\nThe entire argument for working in small steps rests on one property: being wrong must be cheap. Git is where that property comes from, and most people use perhaps a fifth of what it offers.\n\nThe operations below are the ones that turn a bad hour into a lost fifteen minutes. Learn them once and the anxiety about accepting a large generated change mostly goes away, because you can always get back.\n\n## Commit far more often than feels reasonable\n\nA commit is not a publication. It is a save point, and it costs nothing.\n\n```bash\ngit commit -am \"wip: currency column added, tests green\"\n```\n\nCommit after every step that runs. Ten commits in an afternoon is normal and correct. You clean them up before the change goes anywhere:\n\n```bash\ngit rebase -i main        # squash, reorder, reword\n# or, if the history is a mess and the end state is right:\ngit reset --soft main && git commit   # one clean commit, same content\n```\n\nThat second line is the one people do not know. It keeps every change in your working tree, discards the messy history, and lets you write one honest commit message. It converts \"my history is embarrassing\" from a problem into a non-event.\n\n## Stage in pieces, so you review your own change\n\n```bash\ngit add -p\n```\n\nThis walks you through the diff hunk by hunk and asks whether to stage each one. It is the cheapest self-review that exists, it takes ninety seconds, and it is the moment you notice the debug print, the commented-out line, the unrelated formatting change and the `TODO` you meant to resolve.\n\nWith generated changes it does more work than it used to, because you are frequently seeing lines for the first time. `s` splits a hunk into smaller ones; `e` lets you edit exactly what gets staged.\n\n## Work on two things at once without stashing\n\n```bash\ngit worktree add ../myrepo-hotfix main\n```\n\nA second working directory on the same repository, with its own branch, its own files, and no stashing. Your half-finished experiment stays exactly where it is while you fix the urgent thing next door.\n\nThis matters more when running agentic tools, because you can let one work in one worktree while you work in another without your editor and its edits colliding. One caveat that catches people out: dependencies are not shared, so you may need to install them in the new tree, and on some toolchains a symlinked `node_modules` breaks the bundler: copy it rather than linking.\n\n## Find the commit that broke it, automatically\n\n```bash\ngit bisect start HEAD v2.4.0\ngit bisect run pytest tests/test_billing.py::test_total\n```\n\nGit performs a binary search over history, running your command at each step, and tells you the exact commit that introduced the failure. Over a thousand commits that is ten runs.\n\nThis is the single most underused command in git, and it deserves a mention in a course about generated code specifically. When a repository accumulates change faster than anyone reads it, working backwards from a symptom to a cause by reasoning becomes harder, and a mechanical search over history does not care how the commits were produced.\n\nThe requirement is that you can decide pass or fail with one command. Which is another argument for the fast test suite from the previous module.\n\n## Undo, without losing anything\n\n```bash\ngit reset --hard HEAD~1     # discard the last commit's changes\ngit revert <sha>            # a new commit undoing an old one — safe on shared branches\ngit restore --source=HEAD~3 path/to/file.py   # one file, from three commits ago\ngit reflog                  # everything HEAD has pointed at, for ~90 days\n```\n\n`git reflog` is the safety net under the safety net. Almost everything people believe they have permanently destroyed is sitting in the reflog:\n\n```bash\ngit reflog\n# ... 4a3f21b HEAD@{7}: commit: the work you thought you lost\ngit reset --hard 4a3f21b\n```\n\nA hard reset, a bad rebase, a deleted branch, an amended commit: all recoverable, for about ninety days, provided you have not run garbage collection. Knowing this changes how boldly you can operate.\n\n## The rule that ties it together\n\n**Never let an agentic tool run on a dirty tree.**\n\nCommit or stash first, always. When the working tree is clean, `git diff` shows precisely what the tool did and nothing else, `git checkout .` undoes all of it in one keystroke, and there is no possibility of your own uncommitted work being mixed into a change you are about to reject.\n\nThat is one habit, it costs three seconds, and it is what converts a tool that edits your files from something you have to trust into something you can check."},"check":{"question":"A developer lets an agentic tool make changes across twelve files while they had uncommitted work in progress. The result is mostly wrong. Why is this situation much worse than it would have been on a clean tree?","options":[{"text":"Their own work is now interleaved with the tool's, so no single command separates the two.","correct":true},{"text":"Uncommitted changes are not visible to the tool, so it worked from a stale version of the files and its output is inconsistent.","correct":false,"tempting":"Tools read the working tree, so they see uncommitted edits; the problem is separating them afterwards, not the tool's view."},{"text":"The reflog only tracks committed states, so nothing can be recovered and the work is permanently lost.","correct":false,"tempting":"Correct that reflog needs commits, but `git stash` and editor history usually survive, so the loss is recoverability effort rather than certainty."},{"text":"Twelve files exceeds what any review can cover, so the change should have been rejected regardless of the state of the tree.","correct":false,"tempting":"Diff size is a real constraint, but a clean tree would let you reject it in one command, which is exactly what is missing here."}]},"prev":{"slug":"pairing-on-a-real-task","title":"Pairing with a model on real work","url":"https://addaly.com/learn/how-software-changed/pairing-on-a-real-task"},"next":{"slug":"agents-and-blast-radius","title":"Confining an agent","url":"https://addaly.com/learn/how-software-changed/agents-and-blast-radius"},"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) 'Git is what makes speed safe', in How Software Development Changed. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/how-software-changed/git-as-a-safety-net (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":"Git is what makes speed safe","courseTitle":"How Software Development Changed","module":{"index":5,"slug":"the-working-loop","title":"The working loop"},"answer":null,"keyIdea":"Small reversible steps are only cheap if reversal is instant, so commit constantly, stage with `git add -p`, keep a clean tree before letting any tool edit files, and remember that reflog holds almost everything you think you destroyed.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"fast-generation-is-only-safe-when-reversal-is-instant","runs":[{"text":"Fast generation is only safe when reversal is instant"}]},{"t":"p","runs":[{"text":"The entire argument for working in small steps rests on one property: being wrong must be cheap. Git is where that property comes from, and most people use perhaps a fifth of what it offers."}]},{"t":"p","runs":[{"text":"The operations below are the ones that turn a bad hour into a lost fifteen minutes. Learn them once and the anxiety about accepting a large generated change mostly goes away, because you can always get back."}]},{"t":"h2","id":"commit-far-more-often-than-feels-reasonable","runs":[{"text":"Commit far more often than feels reasonable"}]},{"t":"p","runs":[{"text":"A commit is not a publication. It is a save point, and it costs nothing."}]},{"t":"code","lang":"bash","label":"Bash","text":"git commit -am \"wip: currency column added, tests green\""},{"t":"p","runs":[{"text":"Commit after every step that runs. Ten commits in an afternoon is normal and correct. You clean them up before the change goes anywhere:"}]},{"t":"code","lang":"bash","label":"Bash","text":"git rebase -i main        # squash, reorder, reword\n# or, if the history is a mess and the end state is right:\ngit reset --soft main && git commit   # one clean commit, same content"},{"t":"p","runs":[{"text":"That second line is the one people do not know. It keeps every change in your working tree, discards the messy history, and lets you write one honest commit message. It converts “my history is embarrassing” from a problem into a non-event."}]},{"t":"h2","id":"stage-in-pieces-so-you-review-your-own-change","runs":[{"text":"Stage in pieces, so you review your own change"}]},{"t":"code","lang":"bash","label":"Bash","text":"git add -p"},{"t":"p","runs":[{"text":"This walks you through the diff hunk by hunk and asks whether to stage each one. It is the cheapest self-review that exists, it takes ninety seconds, and it is the moment you notice the debug print, the commented-out line, the unrelated formatting change and the "},{"text":"TODO","code":true},{"text":" you meant to resolve."}]},{"t":"p","runs":[{"text":"With generated changes it does more work than it used to, because you are frequently seeing lines for the first time. "},{"text":"s","code":true},{"text":" splits a hunk into smaller ones; "},{"text":"e","code":true},{"text":" lets you edit exactly what gets staged."}]},{"t":"h2","id":"work-on-two-things-at-once-without-stashing","runs":[{"text":"Work on two things at once without stashing"}]},{"t":"code","lang":"bash","label":"Bash","text":"git worktree add ../myrepo-hotfix main"},{"t":"p","runs":[{"text":"A second working directory on the same repository, with its own branch, its own files, and no stashing. Your half-finished experiment stays exactly where it is while you fix the urgent thing next door."}]},{"t":"p","runs":[{"text":"This matters more when running agentic tools, because you can let one work in one worktree while you work in another without your editor and its edits colliding. One caveat that catches people out: dependencies are not shared, so you may need to install them in the new tree, and on some toolchains a symlinked "},{"text":"node_modules","code":true},{"text":" breaks the bundler: copy it rather than linking."}]},{"t":"h2","id":"find-the-commit-that-broke-it-automatically","runs":[{"text":"Find the commit that broke it, automatically"}]},{"t":"code","lang":"bash","label":"Bash","text":"git bisect start HEAD v2.4.0\ngit bisect run pytest tests/test_billing.py::test_total"},{"t":"p","runs":[{"text":"Git performs a binary search over history, running your command at each step, and tells you the exact commit that introduced the failure. Over a thousand commits that is ten runs."}]},{"t":"p","runs":[{"text":"This is the single most underused command in git, and it deserves a mention in a course about generated code specifically. When a repository accumulates change faster than anyone reads it, working backwards from a symptom to a cause by reasoning becomes harder, and a mechanical search over history does not care how the commits were produced."}]},{"t":"p","runs":[{"text":"The requirement is that you can decide pass or fail with one command. Which is another argument for the fast test suite from the previous module."}]},{"t":"h2","id":"undo-without-losing-anything","runs":[{"text":"Undo, without losing anything"}]},{"t":"code","lang":"bash","label":"Bash","text":"git reset --hard HEAD~1     # discard the last commit's changes\ngit revert <sha>            # a new commit undoing an old one — safe on shared branches\ngit restore --source=HEAD~3 path/to/file.py   # one file, from three commits ago\ngit reflog                  # everything HEAD has pointed at, for ~90 days"},{"t":"p","runs":[{"text":"git reflog","code":true},{"text":" is the safety net under the safety net. Almost everything people believe they have permanently destroyed is sitting in the reflog:"}]},{"t":"code","lang":"bash","label":"Bash","text":"git reflog\n# ... 4a3f21b HEAD@{7}: commit: the work you thought you lost\ngit reset --hard 4a3f21b"},{"t":"p","runs":[{"text":"A hard reset, a bad rebase, a deleted branch, an amended commit: all recoverable, for about ninety days, provided you have not run garbage collection. Knowing this changes how boldly you can operate."}]},{"t":"h2","id":"the-rule-that-ties-it-together","runs":[{"text":"The rule that ties it together"}]},{"t":"p","runs":[{"text":"Never let an agentic tool run on a dirty tree.","b":true}]},{"t":"p","runs":[{"text":"Commit or stash first, always. When the working tree is clean, "},{"text":"git diff","code":true},{"text":" shows precisely what the tool did and nothing else, "},{"text":"git checkout .","code":true},{"text":" undoes all of it in one keystroke, and there is no possibility of your own uncommitted work being mixed into a change you are about to reject."}]},{"t":"p","runs":[{"text":"That is one habit, it costs three seconds, and it is what converts a tool that edits your files from something you have to trust into something you can check."}]}],"check":{"question":[{"text":"A developer lets an agentic tool make changes across twelve files while they had uncommitted work in progress. The result is mostly wrong. Why is this situation much worse than it would have been on a clean tree?"}],"options":[{"text":[{"text":"Their own work is now interleaved with the tool’s, so no single command separates the two."}],"correct":true,"tempting":null},{"text":[{"text":"Uncommitted changes are not visible to the tool, so it worked from a stale version of the files and its output is inconsistent."}],"correct":false,"tempting":[{"text":"Tools read the working tree, so they see uncommitted edits; the problem is separating them afterwards, not the tool’s view."}]},{"text":[{"text":"The reflog only tracks committed states, so nothing can be recovered and the work is permanently lost."}],"correct":false,"tempting":[{"text":"Correct that reflog needs commits, but "},{"text":"git stash","code":true},{"text":" and editor history usually survive, so the loss is recoverability effort rather than certainty."}]},{"text":[{"text":"Twelve files exceeds what any review can cover, so the change should have been rejected regardless of the state of the tree."}],"correct":false,"tempting":[{"text":"Diff size is a real constraint, but a clean tree would let you reject it in one command, which is exactly what is missing here."}]}],"order":[2,1,3,0],"why":null},"prev":{"slug":"pairing-on-a-real-task","title":"Pairing with a model on real work","minutes":10},"next":{"slug":"agents-and-blast-radius","title":"Confining an agent","minutes":10}}}