{"thread":{"id":"63534","subject":"Feature request - optionally include merge conflicts in `git add -p`","startedAt":"2025-05-27T23:55:06Z","lastAt":"2025-05-31T05:51:31Z","messageCount":3,"participants":["Jarrad Whitaker","Chris Torek"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"519032","messageId":"CAOWMAxwQacT=PcPrWiuEJzSH24ELDBxq2XMNX1X75KSp6cS6jw@mail.gmail.com","threadId":"63534","inReplyTo":null,"subject":"Feature request - optionally include merge conflicts in `git add -p`","fromName":"Jarrad Whitaker","fromEmail":"jarrad.whitaker@gmail.com","sentAt":"2025-05-27T23:54:54Z","receivedAt":"2025-05-27T23:55:06Z","isPatch":false,"sender":{"key":"jarrad.whitaker@gmail.com","avatar":null},"body":"Hey all,\n\nA small feature request (and hopefully not a small \"tell me I misread\nthe docs\"!) -\n\nWhen fixing up conflicts from a rebase / cherry-pick / etc, files with\nconflicts are excluded from `git add`. This is very sensible in almost\nall cases.\n\nHowever, would it make sense to make a special case for `git add -p` /\n`git add --patch` / patch mode of `git add --interactive`? There seems\nno reason to exclude potentially unmerged files in mode, which goes\nthrough changes chunk by chunk.\n\nExcluding these files means I need to manually go copy paste the\nfilename into `git add` (the horror), and additionally I don't get the\ngreat chunk-by-chunk feedback from patch mode above.\n\nCheers!\nJarrad\n"},{"id":"519034","messageId":"CAPx1GvdqVpmw2mjbSDyVqBii4y=_DpTpa3jXM4_1tg5h5BWF4w@mail.gmail.com","threadId":"63534","inReplyTo":"CAOWMAxwQacT=PcPrWiuEJzSH24ELDBxq2XMNX1X75KSp6cS6jw@mail.gmail.com","subject":"Re: Feature request - optionally include merge conflicts in `git add -p`","fromName":"Chris Torek","fromEmail":"chris.torek@gmail.com","sentAt":"2025-05-28T00:56:24Z","receivedAt":"2025-05-28T00:56:39Z","isPatch":false,"sender":{"key":"chris.torek@gmail.com","avatar":"https://avatars.githubusercontent.com/u/16826774?v=4"},"body":"On Tue, May 27, 2025 at 4:55 PM Jarrad Whitaker\n<jarrad.whitaker@gmail.com> wrote:\n> Hey all,\n>\n> A small feature request (and hopefully not a small \"tell me I misread\n> the docs\"!) -\n>\n> When fixing up conflicts from a rebase / cherry-pick / etc, files with\n> conflicts are excluded from `git add`. This is very sensible in almost\n> all cases.\n>\n> However, would it make sense to make a special case for `git add -p` /\n> `git add --patch` / patch mode of `git add --interactive`? There seems\n> no reason to exclude potentially unmerged files in mode, which goes\n> through changes chunk by chunk.\n\nThere's a technical problem here. It's not that it's a bad idea, but rather\nan issue with how the index / staging-area works combined with how\nmerging and merge conflicts work.\n\nWe'll assume you already know when and why a merge conflict occurs,\nso we'll leap right into the internals here: *when* a merge conflict\noccurs, Git records this by storing all three (well, technically, at least\ntwo and usually three) versions of that particular file in the index.\n\nNormally -- by which I mean, \"when not in conflict state\" -- the index\nhas a single copy of each file that will be in the *next* commit. Git\ncalls these \"stage number zero\" (well, in a few places, but usually\nit's not called out at all).\n\nLet's start by considering a fresh checkout of some particular commit,\nin a newly made clone.  We pick some branch name (main, master,\nwhatever), which picks out the latest commit that's \"on\" that branch,\nand that commit contains a full copy of every file that should get\nchecked out (assuming non-sparse mode etc). In order to extract\nthat commit, Git first copies each file to the index / staging-area,\nfrom that particular commit.  It then copies each file from there to\nyour working tree.\n\n(Aside: the \"copies\" inside commits and in the index are in a special,\ncompressed and de-duplicated form, and since the index copies are\nexact duplicates of the committed copies, the index copies take zero\nextra space. So while it seems profligate to keep *three* copies, there\nare really only two using up disk space: the unchangeable one in the\ncurrent commit, and the changeable copy in your working tree. The\ntwo copies are necessary if one is to be changeable.)\n\nThe three copies all match up, so `git diff --staged` and `git diff`\nand `git status` and so on all claim there are no changes to add\nor commit. But in principle, at least, there are these three copies,\nand that's how Git likes it.\n\nIf we now do some work -- make some changes to a working file\ncopy -- and run `git add`, what Git does at this point is to replace\nthe index / staging-area copy with a new *not*-changeable, ready-\nto-commit copy of the working tree version. There are still those\nthree active copies of each file, but now the index and working\ntree versions match while both of those versions differ from the\ncurrent-commit version.\n\nWhen you make a new commit, Git makes a new snapshot from\nwhatever is in the index / staging-area at that time. These copies\nare all de-duplicated, so every file that exactly matches the previous\ncommit takes no space. Only files you changed-and-added take up\nmore space (which, technically, got allocated at the time you ran\n`git add`). The new commit gets added to the current branch, and\nthe branch name now \"means\" the newest and latest commit, and\nwhen all of this is done, all three copies of every file match again\n(assuming you `git add`-ed all your changes, of course).\n\nAt some point though, you run `git merge` and hit a merge conflict.\nGit needs to store the fact that there *is* a merge conflict, so that\nyou can't commit the result until you resolve the conflict. (Aside:\nthis *might* be a design mistake of sorts. But that's the design.)\nAnd now we get into the *way* Git stores this fact: Git takes the\nthree versions of the to-be-merged but conflicted file, and stores\n*all three* versions of that file in the index, under the name the\nfile will have once the merge is resolved.  These three versions\nget numbered: stage 1 is the \"merge base\" copy, stage 2 is the\n\"ours\" copy, and stage 3 is the \"theirs\" copy. (At most one of these\nthree higher-stage copies can be missing: in an add/add\nconflict there's no stage-1 version, while a modify/delete conflict\nmeans there's no stage-2 or stage-3 version.)\n\nGit attempts the merge and stores the messy, conflict-ridden result\nin the working tree. But let's go back to our earlier view, where there\nwere three copies of each file: the HEAD (current commit) version,\nthe staging/index \"stage zero\" version, and the working-tree version.\nBut now there are *three* index copies, so in the conflicted state,\nthere are now *five* versions of the file: HEAD (current commit),\nmerge base (from the merge base commit), \"ours\" (same as HEAD),\n\"theirs\" (same as third commit), and working tree. Except for the\nworking tree copy, all are duplicates and take no space except for\nthe index entries and staging number.\n\nAssuming you resolve the conflict manually as usual, you would then\nrun `git add`. When you `git add` the working tree copy, Git throws\nout the stages 1, 2, and 3 entries entirely and makes a de-duplicated\ncopy of whatever is in the working tree and stuffs that into the index\nas \"stage zero\".\n\nThis is the heart of our technical problem: the existence of a\n\"stage zero\" entry means the file is not conflicted, and the existence\nof stages 1, 2, and/or 3 entries means the file *is* conflicted and\n`git commit` is forbidden. Git refuses to allow for *four* copies in\nthe index, i.e., having both stage zero *and* higher-numbered\nstages is an error. But `git add -p` would need to do something like\nthat: have a partially-resolved copy (perhaps containing the same\nconflicts that show up in the working tree?) along with the three\nbase/ours/theirs copies. You would also have to define how this\nwould interact with other existing Git commands (e.g., `git restore`).\n\nNow, there is of course no technical reason these internal\ntechnical details could not be *changed* in some way, so as\nto accommodate your vision. But as things stand now, it's a\nproblem, and making any changes to the way the index works\nis a very big job. If you're volunteering to do it... :-)\n\nChris\n\n(Additional footnote: Git's original design alluded to the possibility\nof having more than one \"stage 1\" index entry for a file, when there\nis more than one merge base, or multiple \"stage 3\" entries when\ndoing octopus merges. But this has never been implemented.)\n"},{"id":"519305","messageId":"CAOWMAxwqQepzcGpuocLA+d2=+AAdLYDOswsNr3w8xaO9zZFqqQ@mail.gmail.com","threadId":"63534","inReplyTo":"CAPx1GvdqVpmw2mjbSDyVqBii4y=_DpTpa3jXM4_1tg5h5BWF4w@mail.gmail.com","subject":"Re: Feature request - optionally include merge conflicts in `git add -p`","fromName":"Jarrad Whitaker","fromEmail":"jarrad.whitaker@gmail.com","sentAt":"2025-05-31T05:51:18Z","receivedAt":"2025-05-31T05:51:31Z","isPatch":false,"sender":{"key":"jarrad.whitaker@gmail.com","avatar":null},"body":"As a thought experiment, what would happen if the first invoked stage\noperation of `add -p` nuked the stage-1/2/3 versions? Would this work?\nThe file would be recorded as no longer conflicting, is that too\nfootgunny?\n\nApologies for the double-send and top-posting, Chris; I responded from\nmy phone's gmail without thinking.\n\nJarrad\n"}]}