Suppose I have my "master" branch checked out. I've committed some production changes to "master", and now I want to rebase my "experimental" branch onto the latest master. But, I want to do this without modifying any files in my working copy. Essentially, I want all the magic to happen inside the .git directory, without touching the working copy.
If not for the "don't modify my working copy" requirement, this would just be a matter of doing:
# current branch is master
git checkout experimental
git rebase master
git checkout master
My real problem is that this modifies timestamps in my working copy, even though I'm ending by checking out the exact same content I started with. As soon as I run "git checkout experimental", any files that contain changes in the experimental branch will get their mtime set to the current time -- and so will any files that were changed in master since the last time I rebased experimental. Because the mtimes have changed, things like build tools get the idea that there's work they need to do again, even though, by the time I'm done, the files' contents haven't actually changed. (In my case, it's that if a project file's timestamp changes, Visual Studio thinks it needs to spend a lot of time unloading and reloading the project.) I want to avoid that.
Is there a way to do all of the above in one step, without ever modifying anything in the working copy (assuming there are no conflicts during the rebase)?
(If there are conflicts, my preference would be to show the error and then abort the entire operation, without ever modifying any timestamps. But that's just my preference, not a hard requirement -- I don't know what all is possible.)
Of course I can write a script to capture the mtimes, run git, and then reset the mtimes; but it seems likely that Git would already have a way to do things like rebase without bothering the working copy, since the rebase is really about the deltas, not the files' actual contents.
Is there a way to do all of the above in one step, without ever modifying anything in the working copy?
This is unfortunately impossible (without creating a modifiable copy of the working copy - see also Petr's answer), because git performs all merge-y operations (real merges, cherry-picks, rebases, patch application) on the work tree. This is mentioned several times before, for example in one of the knowledgeable Jakub Narębski's answers:
There is no way that merge (or rebase) can work without touching the working directory (and index), as there can be merge conflicts that have to be resolved using working directory (and/or index).
Yes, it's a design decision, but it's a pretty understandable one - it'd be a bit of a chore to build up all the structure necessary to attempt a merge in memory, then as soon as it hits a conflict, dump everything into the work tree, when instead you could simply do it in the work tree in the first place. (I'm not a git developer; don't take this as absolute complete truth. There could be other reasons.)
My suggestion, rather than writing a script to do all that mtime manipulation, would be simply to clone the repository, perform the rebase in the clone, then push it back into your original repository:
git clone project project-for-rebase
cd project-for-rebase
git branch experimental origin/experimental
git rebase master experimental
git push origin experimental
That of course assumes that experimental isn't checked out in your original repo. If it is, instead of the push, you'd do something like git fetch ../project-for-rebase experimental; git reset --hard FETCH_HEAD or more readable, git remote add for-rebase ../project-for-rebase; git fetch for-rebase; git reset --hard for-rebase/experimental. That will naturally touch whatever files differ between the original and rebased experimental branches, but that's definitely correct behavior. (This wasn't the example you gave, of course, but I want these instructions to be general!)