What's the best way to have a "temporary" commit in git?
Say I have a project with two branches master and dev. I have a bunch of commits on dev for a special event which once tested are merged into master. Then after the event is over I want to remove the event specific code. However a git reset won't do since other commits have been made since the event code was added.
Currently I use git checkout to开发者_JAVA百科 checkout the files from before the event was merged in and then use git diff to re-add in the changes that have been made since the event was committed. This seems like a very messy method to me.
Does anyone have a better solution for having temporary code in a project?
Edit: To be clear the changes need to be committed, pushed, uncommitted, pushed.
Take master and create a branch: git checkout -b special-event
, make/apply your changes. Once the event is over, simply switch back to master and abandon/delete the branch.
In order to continue making changes, make them on master and merge them into the special branch as you go. git checkout master
... make changes ... git checkout special-event; git merge master
.
Alternatively, make all your special-event related changes in one commit, then use git revert
when you want to roll them out and specify just that commit.
"Temporary code"? In git we use branches for that. Commits are very lightweight in git, and branches, being just names (or references) for commits with a tiny bit of logic attached, are super lightweight compared to other systems.
The tiny bit of logic is just that the reference is automatically updated to the last commit. That's pretty much all of what a branch is. That means creating and destroying branches in git is fast, simple and even safe, since nothing is really created or deleted. After a delete you can still reference to the branch head by commit-id (yes, it's still there until it's garbage-collected and even that happens only if there's no other reference to it).
Almost every command in git takes references to a commit as parameters. That's true for git merge as well. You're not merging branches, you're merging commits. Yes, you type 'git merge ', but again, a branch is just a name for a commit. Nothing more than that. And since all you need is grouping the commits for the event together, just give them a name.
So the right thing to do is to create a branch for the event. Or possibly, two: 'event-master' and 'event-dev'. Now, you develop code for the event in 'event-dev'. Should you bump into a bug that needs to be fixed in the main code, stash and switch to your normal 'dev' branch, code the fix in and commit. Switch back to 'event-dev', merge from 'dev', and pop the stash. Go on developing, once done, commit and test. If it's ok, merge 'event-dev' into 'event-master'. This would contain also the fix. Note that the fix is not in 'master' yet.
If you need the fix merged into 'master', you just do it the usual way, as the fix is in 'dev'.
Basicly in this setup you can:
develop the main code as usual: you commit to 'dev', test, and merge into 'master';
develop the event specific code in a similar manner: you commit to 'event-dev', test, and merge into 'event-master'; it's just the same workflow;
intermix the two workflows; commit and switch branch, or use git stash;
merge branches (almost) freely; you can merge 'master' into 'event-dev', if master is somehow updated and you need the changes for the event; you can merge 'dev' into 'event-dev', if you desperately need the changes for the event and can't wait for the usual testing cycle that pushes them into 'master'. git would do this as far as the merges are clean, that is unless you changed the same piece of code in two different ways in the two -dev branches (this of course is a special case that needs to be handled);
If you need extra flexibility, create more branches. For super flexibility, you can cherry-pick individual commits into a branch, but in general I advise against that (do it only if you really know what you're doing).
Finally, I should point out that this workflow is so natural in git. Actually, developing in 'dev' and pulling changes in 'master' is not even the best generally speaking. It's customary to create a dev branch for each feature or module you're developing, and merge those branches in 'dev'.
I think the right mindset when using git is: "what do I feel like to code today? feature X. Good, let's create branch 'feature-x' and start hacking.". That event of yours is no different.
Now, I know that what I'm telling you is how you should have done things from the start, and that doesn't help much right now. You need to fix your tree, since - so it seems - you have the changes for the event mixed with the normal changes into your 'dev' branch. So your problem is how to properly create 'event-dev' with only the changes for the event and remove them from 'dev' at the same time.
Time to rewrite history. How hard that will be, it depends on the nature of the changes. For example, if all changes belong to a single directory, things can be easier.
Here's what I would do (knowing no better):
examine the history with git log;
find the commit that's right before the first event specific change, take notice of its commit-id: this is the 'day-0' commit;
create a branch named 'newdev' pointing at that commit. Do this in a clean tree, that is commit before doing this: git checkout <commit-id> -b newdev;
create 'event-dev': git checkout -b event-dev;
you've got now two branches both pointing at the 'day-0' commit;
now look at the history again (git log dev), you need to follow it commit by commit;
I'm assuming that each commit following 'day-0' is either a commit that belongs to the main code, or one that belongs to the event only. What you have to do is to cherry-pick them in the right branch, 'newdev' if it's main code, 'event-dev' if it's event-code; do it one a time, in the right order (from 'day-0' to today);
if you're very lucky, no commit that ends up in 'newdev' depends on commits in 'event-dev' and viceversa; you're kinda done; you may want to rename (keep them around) the current master and dev to master-old and dev-old, then rename newdev to dev, create master from dev, event-master from event-dev, and you're set;
if you're a bit less lucky, at times you'll have to merge 'newdev' into 'event-dev', because some commits depends on changes made in the main code, which is fine, tho; if you're feeling daring here, it's time to read about git rebase; but rebase don't unless you have to;
or (worse) some commits in 'newdev' depend on changes in 'event-dev'... oops, that event-specific code turns out to be not so event-specific, if the main branch needs it. Some merging required;
or (bad) one commit contains both kind of changes: divide and conquer (you need to separate the changes and apply them to the right branch), that means you're splitting the commit in two;
or something else I can't imagine now since I don't have enough details about your tree.
It could be a breeze or a nightmare. Inspect each commit beforehand (git show, you can look at it as a patch), decide what to do (eventually don't cherry-pick, just editing the files could be easier), if not sure - guess what - create a branch, work in there, see what happens, merge it if happy, drop it otherwise and try again.
I haven't mentioned it so far, but of course you can make a copy of the whole tree, git files included, and work on the copy to be 100% safe.
Doing it right from the start is rather easy. Fixing it now, well, good luck. :)
Roll up your commits following my not-patented method:
# work on a new branch
git checkout -b event-36
git commit -a -m "all broken dammit"
git commit -a -m "fixed that damn code"
git commit -a -m "almost ready, going to eat lunch"
git commit -a -m "It's done!"
# other people might be working on master in the meantime, so
git checkout master
git checkout -b erasemeImmediately
git merge event-36
git reset --soft master # THE KEY TO THE WHOLE THING: SOFT RESET!
git commit -a -m "Event 36, all code in one commit"
git checkout master
git merge erasemeImmediately
You can do this with reflog
, but then you'll need a recent CS degree (i.e, it's hard to understand).
Now your commit is one single one. Now you can use revert or do cherry-pick your way to a branch without it.
I beleive stash does what you want.
git checkout -b event
...make specific event change...
git commit -am "specific event change"
...make another specific event change...
git commit -am "another specific event change"
At this point, the master branch is still intact and the event specific changes are on the event branch. If changes are made to the master branch that are also needed in the event branch, use rebase...
git rebase master
Another answer suggested merging master into event but rebase is usually the better approach. A rebase peels the commits made on the topic branch off, pulls the the updated master forward and then reapplies the topic branch changes on top...as if the topic branch changes were made to the most recent version of master.
In your scenario, when the event is over, simply delete the event branch.
git revert
It will remove any commit, or range of commits that you need to undo. It is also safe to push to a public repo.
In my experience, some special events have a way of recurring. So an alternate solution to consider here is to create a configuration option to enable handling the special event, so that it can be turned on and off as and when needed.
精彩评论