2

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.

talrnu
  • 213
  • 1
  • 10
  • 2
    I do realise how unhelpful comments saying, 'don't do this,' are, but I'd strongly recommend that the better option would be training developers to commit often on their own. This just sounds like it will encourage poor practice. Also, what happens if something has changed upstream on `%BRANCHNAME%`? You'll need to rebase or merge your `autocommit` branch first, won't you? – DaveyDaveDave Dec 23 '15 at 14:17
  • @DaveyDaveDave A valid point, I'd tend to agree. In this case, the developer does exercise good practices, they'd just also like some conveniently automated system to make use of git power to help their personal workflow by backing up their local work at high frequency for extensive history. The autocommit branch will ideally never be merged into another, or indeed be involved in any operation other than the auto-commit process and historical review. – talrnu Dec 23 '15 at 14:32
  • As for the effects of upstream changes, I assume the developer will be mindful and manage synchronization of the autocommit branch with some degree of intelligence. Unless there's a detail I'm not considering (very possibly - I'm no git guru), I'm satisfied with ignoring that bit of complexity in the autocommit process. – talrnu Dec 23 '15 at 14:35
  • I might well be wrong here, I'd have to test it to be sure, but let's say the `autocommit` branch is at commit `C1` and I pull on `master` taking it to `C5`, with file `foo` updated. If I then change `foo` myself and attempt to commit it to `autocommit`, without the changes from `C2` to `C5`, won't there be a horrible conflict? I must admit to being biased having had horrible experiences with `git up` automatically stashing things and getting it wrong 90% of the time, so perhaps it's not as bad as I'm thinking... – DaveyDaveDave Dec 23 '15 at 14:46
  • @talrnu, it would make a lot of sense for each commit to be a small _meaningful_ change. If it's just a lot of small fragmented changes, it's actually counter-productive. Think this through! – Shahbaz Dec 23 '15 at 14:53
  • I've added some comments acknowledging the deviation from standard git philosophy of this script. I'm really just looking for an answer to my specific question, I understand I'm not using git as it's designed to be. – talrnu Dec 23 '15 at 15:46

1 Answers1

1

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.

oyvind
  • 1,429
  • 3
  • 14
  • 24
  • Nice, didn't think of that approach, works like a charm. I made a couple small edits to your answer based on test results. Yes, it's intended for local use only, though with more squashing occasional merging to a per-developer remote backup branch could be feasible. Thanks! – talrnu Dec 29 '15 at 20:24
  • Apparently my edits were rejected, and it was recommended that they instead be posted as comments: 1. The `git reset --hard HEAD~2` needs to be `...HEAD~1`, or else you end up one commit too far back. 2. You'll only get a lot of junk files if you don't have a properly configured .gitignore. With a good .gitignore, as I tested and confirmed, this script works perfectly regardless of build output files. – talrnu Dec 29 '15 at 23:45
  • @talrnu The HEAD~2 thing might be because the merge becomes a fast forward merge. You should try adding `--no-ff` to the first merge. – oyvind Dec 30 '15 at 00:20
  • I see, `~2` fails in my testing because I'm only testing the first auto-commit. At that point, the only commit in the autocommit branch is already in my working branch, so merging it back into my working branch doesn't do anything at all because there are no changes to merge in. `~1` works because there's only one commit to reset at that point, but after an auto-commit happens `~2` works fine. If I run an auto-commit and then immediately commit to my working branch, I'll run into the same problem on the next auto-commit. Any ideas for how to handle that? – talrnu Dec 30 '15 at 03:26
  • Tested with `--no-ff`, still doesn't work. My test starts with a new repo, I add 3 files (and a .gitignore, configured to ignore one of those) and create the first commit, create the autocommit branch, modify some files (stage one, leave one unstaged, and create a new untracked file, leaving the ignored one ignored), then manually run the individual commands from your script as it currently appears. The first merge in the autocommit process (with `--no-ff`) yields an "already up to date" message and no merge occurs. Makes sense - at this point the autocommit branch is already merged in. – talrnu Dec 30 '15 at 18:59
  • So I was wrong, that won't be a problem after committing to the working branch, unless the user decides to merge the autocommit branch into the working branch for some reason, which I'm willing to require them to deal with. But the initial autocommit should ideally not require any manual intervention. – talrnu Dec 30 '15 at 19:09
  • You can add a dummy commit after creating the branch. – oyvind Dec 30 '15 at 23:01