Search code examples
gitintellij-idea

Git CL equivalent to IntelliJ Partial Git Commit


With the Commit tool window in IntelliJ, you can do partial git commits, meaning you can commit only some files with changes, or even certain certain chunks of changes within changed files. I'm familiar with git's interactive adding and patching abilities, but those commands add changes to the index, after which issuing a commit will commit the whole index, not just the result of the patch. IntelliJ, however, seems like it can somehow do the patch but also only commit just the selected changes, ignoring other changes that may be in the git index, and I'd love to know what set of git CL commands can produce an equivalent behavior. Maybe something like stashing the index, unstashing the selected file(s), doing the patch, commit, and then unstashing the rest of the stash? Is there an easier way to accomplish this?


Solution

  • Every Git commit is a full snapshot of all files (plus metadata). That means that for IntelliJ to make a Git commit, it must make a full snapshot—so it must do the same thing that git add -p does.

    You can, however, create a temporary index, in addition to the standard index that Git assigns to your working tree. Note that git worktree add actually adds both a working tree and a new index for that working tree. There is also a private HEAD for the added working tree, and some per-worktree refs for bisection and so on; the details get a bit complicated. But overall each working tree has one distinguished index–"the" index—and that's the one that git commit will use by default.

    To make a temporary index, you would:

    1. Create a temporary index file (well, file-name in most cases, but you can copy "the" index for the working tree to this file; you just can't have an empty file as Git rejects that, so if you use mktemp from a shell script, you have to either remove the file, or copy the regular index to it).

    2. Populate that temporary index as desired: set the environment variable GIT_INDEX_FILE to hold that file's name while using git reset and/or git restore and/or git add and so on, to adjust this temporary index's content. (Note that you must not dilly-dally too much here: changes made in this temporary index might be wrecked by a git gc if you do not complete everything within 14 days. That's generally true for all tricky Git hackery, such as using git write-tree and git commit-tree.)

    3. Use git commit with this same GIT_INDEX_FILE setting, to make a new commit from the temporary index, rather than from the real index.

    4. Do something about the real index: it's now badly out of sync with the current commit.

    The real hard part here is step 4. You must figure out what to do, how to do it, and how to roll it back in case of a disaster such as the disk drive running out of disk space.

    The git commit command itself has --only and --include options that do steps 1 through 4 internally during the git commit action. For instance, you can run:

    git add file1.ext file2.ext
    # edit file3, realize you want to commit *just* the file3 changes
    git commit --only file3
    # go back to working with files 1, 2, adn 3
    

    This sometimes involves just the two index files—the real one and a temporary one—and sometimes, to handle the complications mentioned above for step 4, involves making three index files. Once the git commit action is finished (with or without error), one of the two or three index files becomes the "real" index file for this work-tree.

    What IntelliJ does internally, I have no idea, but the above covers the visible-to-Git parts of what must be done.