Search code examples
gitbranchrebasecherry-pick

git cherry-pick commits in range where commit message contains string / matches regex?


Background / scenario

I have a local work flow where

  • I always commit on a "local" branch (ancestor of "master"), using commit messages beginning with issue number, such as "#123: Some message".
  • I create feature branches which are also ancestors of "master".
  • I then use rebase and/or cherry-pick to copy the commits from local onto the respective feature branch.

E.g. one way to do this:

git checkout local
git checkout -b 123-my-feature
git rebase -i master

In the interactive rebase editor, remove all commits where the message does not begin with #123. This leaves a feature branch 123-my-feature which only contains commits from the issue #123.

Later, after the branch 123-my-feature already exists, subsequent commits on local also need to be picked / copied onto the 123-my-feature branch, if the message begins with #123. This is also possible with interactive rebase, or with individual cherry-picks. But the process is cumbersome.

Question

Is there a way to do this with cherry-pick, and automatically filter by commit message?

E.g.

git checkout master
git checkout -b 123-my-feature
git cherry-pick master..local --commit-message-begins-with="#123"

Or maybe even an interactive cherry-pick, where I can manually remove commits not beginning with "#123".

If there is another command which achieves this, why not. Doesn't have to be cherry-pick.


Solution

  • Not directly, no—but it's easy to write a script that would do it.

    The general purpose command behind most other Git commands is git rev-list, which has a huge option list. The first thing to know about git rev-list, though, is that it's actually almost the same command as git log. They both take the same options, and do mostly the same things, except that git rev-list is aimed at producing hash IDs—mostly those of commits— for other Git programs, while git log is aimed at showing you, or some other human, the contents of commits.

    Since git log has --grep which lets you select commits to show based on messages, git rev-list also has --grep which lets you select commits. Using the pattern '^#123: ', for instance, would select commits whose log message has a line that starts with 123:  (including a trailing space—choose your pattern here and use git log to test it).

    Like git log, git rev-list will work backwards from whatever starting (or ending?) point you give it, and keep going forever, unless you tell it where to stop searching too. You need to pick a stop-point; usually this would be a branch name, or tag, or perhaps even a raw hash ID, to select a commit where Git should terminate its backwards search for commits. In this case, that stopping point might be master. The general syntax for this is just what you wrote: master..local (or equivalently, local ^master). Hence the list of commits you want is that generated by:

    git rev-list --grep=... master..local
    

    (try running this, or—even better—run git log first, and then run git rev-list and observe the difference).

    Now, there's one or two remaining issues. The first is that like git log, and pretty much most of Git, git rev-list works backwards, starting from the newest commits and going back in time. But when you are cherry-picking, you tend to have to start at the earliest point in time and work forwards. Fortunately git log has --reverse, so git rev-list also has --reverse.

    The last obvious issue is that it's possible that git rev-list might find no commits. In this case, you can let git cherry-pick itself complain, or you can check and do your own complaint. To just let git cherry-pick complain:

    git checkout -b 123-my-feature master
    git cherry-pick $(git rev-list --reverse --grep '^#123' master..local)
    

    (I've combined the branch-creation of 123-my-feature with the setting of its initial value, to point to the same commit as master, so that the whole thing is just two lines of shell script. To do this in a fancier way, you might capture the git rev-list output first, and only create the branch and run the cherry-pick if the list is not empty.)