Search code examples
gitgit-commitgit-stashpost-build-event

Apply stash with untracked files already committed


I've been asked to write a script to run in post-build events in Visual Studio that will respond to an output-updating build by committing all changes, including new (untracked) files, to a local "autocommit" branch. The idea is to help the lazy developer backup buildable code frequently so they can avoid losing their work.

My current approach (see snippet below):

If the user isn't on the autocommit branch, I stash their changes and untracked files, checkout the autocommit branch, apply the stash, and commit before returning to the previous branch and popping from the stash to return to the initial state.

My problem:

If a file is untracked on the user's current branch, but has already been auto-committed to the autocommit branch, then git stash apply fails to overwrite the file tracked on the autocommit branch with the untracked version in the stash.

From the git stash documentation, it doesn't seem like there are any relevant arguments I could use on the apply call to get around this. I can detect untracked files in the current branch before stashing by parsing the result of a git status --porcelain for lines starting with ??, but that won't tell me which of those are already being tracked on the autocommit branch.

I'm currently required to use Windows batch files, so I'd like to limit my solution to tools likely to be available in that environment on any dev machine.

Here's the relevant snippet from my current approach:

git stash save --include-untracked -keep-index
git checkout autocommit
git stash apply
git add -A
git commit -m "Autocommit of build %VERSION%"
git checkout  %BRANCHNAME%
git stash pop

Deviation from git philosophy

The auto-commit process is intended to serve strictly as a convenience, git-based, auto-save system that doesn't require the developer to touch git or take any additional manual steps every time they rebuild their project successfully.

It doesn't align with normal git philosophy, because it's not intended to be used for source control or code sharing. I simply want to use git to provide snapshots for the developer to revert to e.g. if they lose their project to file corruption. This will lead to a large volume of tiny commits with little individual value, and that's okay - in fact, it's ideal for my needs.

The script assumes that the uncommitted changes on the current branch can be sensibly applied and committed to the autocommit branch. Any reason that assumption is invalid would be caused by the developer's direct interaction with the repo. As part of any such interaction, the developer is responsible for updating the autocommit branch accordingly so that the script's assumptions are valid the next time it's run.


Solution

  • I see some potential problems with your approach:

    1. git stash apply on autocommit might give conflicts which need manual resolution. This will take the "automatic" out of the whole procedure.
    2. The commit on autocommit is not guaranteed to be identical to the current working copy since git stash apply performs a merge.

    The following sequence should solve both those problems and sidestep your original problem.

    git stash -u
    git stash apply
    git add -A
    git commit -m "dummy"
    git merge --no-ff autocommit -s ours
    git checkout autocommit
    git merge - --squash
    git commit -m "Autocommit of build %VERSION%"
    git checkout -
    git reset --hard HEAD~2
    git stash pop
    

    Explanation:

    1. Save the current state of the working copy.
    2. Bring back the working copy.
    3. Stage everything
    4. Make a dummy commit on the current branch. This will be copied to `autocommit' and subsequently delete from the current branch.
    5. Merge autocommit with our changes to facilitate moving the changes. The ours strategy ensures that the result of the merge is identical to the state of the working copy we want to save. There will never be a merge conflict with this strategy. This merge cannot be done into autocommit because there is no theirs strategy.
    6. Move to the autocommit branch.
    7. Bring the changes into the branch. The --squash option is vital. No commit is made, instead all the changes are staged.
    8. Commit the staged changes.
    9. Move back.
    10. Remove the dummy commit and the merge commit.
    11. Return the original state.

    The result should be a single commit on autocommit representing the working copy.

    Be aware that this script likely will commit lots of junk (build files). The autocommit branch will also be very different for different developers. I recommend it remains local to avoid bloating the original repo. If you must push it try to give each developer their own branch name to avoid conflicts.