|
| 1 | +--- |
| 2 | +meta: |
| 3 | + title: How to add an experimental feature to your code, without having it in your main version control |
| 4 | + description: You might have a really good idea, but its not practical to get it commited to version control. You can keep the feature around for local use with some shell aliases and git commands. |
| 5 | + dateCreated: 2025-05-19 |
| 6 | + |
| 7 | +tags: |
| 8 | + - "software_engineering" |
| 9 | +--- |
| 10 | + |
| 11 | +I've been infatuated with loggers for development recently. The idea is, I can put log statements in various parts of my application, and it will log the relevant data to a file. This means no having to set breakpoints and enable/disable my debugger, no scrolling back up the developer terminal. I just leave it running and then when I need it I find the relevant log. |
| 12 | + |
| 13 | +Useful as this is for me, it's not practical for me to convince the rest of the organisation to adopt this pattern formally, not in the pull request I'm working on at least. Besides, I'm still working out what the interfaces should be, and interfaces have this nasty habit of being difficult to change once they're being used. |
| 14 | + |
| 15 | +But I still like the tool and I still want to keep using it. |
| 16 | + |
| 17 | +Here's how I keep it around, without having to commit it to version control proper. |
| 18 | + |
| 19 | +The strategy is that we create one shell alias to cherry-pick a commit in to add the feature, and then another alias to rebase that single commit out. |
| 20 | + |
| 21 | +``` |
| 22 | +A---B (master) |
| 23 | + \ |
| 24 | + C---D---E---F---G (my-branch) |
| 25 | + |
| 26 | + ^ ^ |
| 27 | + the experimental feature commits |
| 28 | +``` |
| 29 | + |
| 30 | +Let's assume commits D and F are ones where I implement the feature. |
| 31 | + |
| 32 | + |
| 33 | +**1. Create a single commit that removes the feature** |
| 34 | + |
| 35 | +We'll call this commit Q. |
| 36 | + |
| 37 | + |
| 38 | +``` |
| 39 | +A---B (master) |
| 40 | + \ |
| 41 | + C---D---E---F---G---Q (my-branch) |
| 42 | + |
| 43 | + ^ |
| 44 | + removes the feature |
| 45 | +``` |
| 46 | + |
| 47 | + |
| 48 | +**2. Revert that commit** |
| 49 | + |
| 50 | +``` |
| 51 | +git revert Q |
| 52 | +``` |
| 53 | + |
| 54 | +``` |
| 55 | +A---B (master) |
| 56 | + \ |
| 57 | + C---D---E---F---G---Q---R (my-branch) |
| 58 | + |
| 59 | + ^ ^ |
| 60 | + removes the feature reverts the removal |
| 61 | +``` |
| 62 | +We'll call this commit R. |
| 63 | + |
| 64 | +Essentially this is a commit that will _add the feature back in_. |
| 65 | + |
| 66 | +The idea is, when we later want to add the feature in, we'll cherry-pick a commit like this to add it in. |
| 67 | + |
| 68 | +However, because we're likely squash merging to master, the commit Q that the revert commit R is reverting, will no longer exist. |
| 69 | + |
| 70 | +ie. after we merge to master, the git tree looks like this: |
| 71 | + |
| 72 | +``` |
| 73 | +A---B---X (master) |
| 74 | +``` |
| 75 | + |
| 76 | +Where X is all |
| 77 | + |
| 78 | +``` |
| 79 | +C---D---E---F---G---Q |
| 80 | +``` |
| 81 | + |
| 82 | +squashed into one commit. |
| 83 | + |
| 84 | + |
| 85 | +**3. Transform commit R to a regular commit** |
| 86 | + |
| 87 | +``` |
| 88 | +git reset HEAD~1 |
| 89 | +git commit |
| 90 | +``` |
| 91 | + |
| 92 | +``` |
| 93 | +A---B (master) |
| 94 | + \ |
| 95 | + C---D---E---F---G---Q---S (my-branch) |
| 96 | +
|
| 97 | + ^ ^ |
| 98 | + removes the feature Adds the feature back in |
| 99 | +``` |
| 100 | + |
| 101 | +We'll call this commit S. |
| 102 | + |
| 103 | +Now, we can selectively call |
| 104 | + |
| 105 | +``` |
| 106 | +git cherry-pick S |
| 107 | +``` |
| 108 | + |
| 109 | +to have our feature apply. |
| 110 | + |
| 111 | + |
| 112 | +**4. Create shell aliases to add and remove the commits** |
| 113 | + |
| 114 | +Add these to your `.bashrc` or `.zshrc` etc. |
| 115 | + |
| 116 | +``` |
| 117 | +feature_commit=S |
| 118 | +feature_commit_message=some-unique-commit-message-here |
| 119 | +alias addfeature="git cherry-pick $feature_commit && git commit --amend -m $feature_commit_message" |
| 120 | +alias removefeature='added_commit_sha=$(git log --grep="$feature_commit_message" --pretty=format:%h); git rebase --onto "$added_commit_sha"^ "$added_commit_sha"' |
| 121 | +
|
| 122 | +``` |
| 123 | + |
| 124 | +How this works: |
| 125 | + |
| 126 | +Adding the feature is pretty straightfoward. The content of commit S exists in our local git object store, and git can retrieve it via the SHA S. |
| 127 | +When we apply the commit, git will actually give it a different SHA. That's why need to also rename the commit message, so we can later find this applied commit by commit message, not by SHA. |
| 128 | + |
| 129 | +Removing the commit now involves searching the git log for our unique commit message, and then we do a rebase to remove the commit. |
| 130 | + |
| 131 | +We don't want to do a revert, because we don't want to pollute our git history with a whole bunch of applying the feature and removing it. It'll be squashed away anyway, but still. |
| 132 | + |
| 133 | + |
| 134 | +**5. Tidy up** |
| 135 | + |
| 136 | +Manually remove commit S |
| 137 | + |
| 138 | +``` |
| 139 | +git rebase -i HEAD~1 |
| 140 | +``` |
| 141 | + |
| 142 | +**6. Create a remote branch containing commit S** |
| 143 | + |
| 144 | +The solution works by commit content existing in your local git object store. |
| 145 | + |
| 146 | +If your laptop died and you hadn't pushed it remotely, you'll have lost it. |
| 147 | + |
| 148 | +**7. You're ready to go!** |
| 149 | + |
| 150 | +Apply the feature with |
| 151 | + |
| 152 | +``` |
| 153 | +addfeature |
| 154 | +``` |
| 155 | + |
| 156 | +Remove the feature with |
| 157 | + |
| 158 | +``` |
| 159 | +removefeature |
| 160 | +``` |
| 161 | + |
| 162 | +Note that you may need to deal with merge conflicts when you apply the feature. |
| 163 | + |
| 164 | + |
| 165 | +**8. Updating the feature later** |
| 166 | + |
| 167 | +Apply the feature |
| 168 | + |
| 169 | +``` |
| 170 | +addfeature |
| 171 | +``` |
| 172 | + |
| 173 | +Make your changes. |
| 174 | + |
| 175 | +Amend the commit to include the new changes. |
| 176 | + |
| 177 | +``` |
| 178 | +git add -A |
| 179 | +git commit --amend |
| 180 | +``` |
| 181 | + |
| 182 | +Update your shell rc script to use this new commit SHA. |
| 183 | + |
| 184 | + |
| 185 | + |
| 186 | +Let me know if you find this helpful! |
0 commit comments