Derp Derp - 5 days ago 5
Git Question

How to use --commit-filter to exclude 20 commit in one git filter-branch pass?

I need to clean 20 commits in a row from git history in bare repository.
I try to use the following command, (notice, i do not want to write full-length hashes and try to use sub-string comparison):

git filter-branch -d /dev/shm/scratch --commit-filter '
if [ ${GIT_COMMIT0:7} = e687d59 ] ||
[ ${GIT_COMMIT0:7} = 58daf29 ] ||
...
[ ${GIT_COMMIT0:7} = 682b2c2 ] ||
[ ${GIT_COMMIT0:7} = 947db9e ];
then
skip_commit "$@";
else
git commit-tree "$@";
fi' --tag-name-filter cat -- --all


But i instantly get message like:


Rewrite 414840693169a74d052548c7a7a1bc5b06d3b79c (1/10360) (0 seconds
passed, remaining 0 predicted) git commit-tree: 49: git
commit-tree: Bad substitution could not write rewritten commit


There is somweting wrong either with OR condition or with sub-strings, i can't figure out.

I also have additional question: how can i get log file with mapping old hash to a new one when
git filter-branch
completed it's job?

Answer

The basic problem is that this:

if [ ${GIT_COMMIT0:7} = e687d59 ] ||
   [ ${GIT_COMMIT0:7} = 58daf29 ] ||
   ...
   [ ${GIT_COMMIT0:7} = 682b2c2 ] ||
   [ ${GIT_COMMIT0:7} = 947db9e ];

is wrong.

The bash syntax for substrings is ${name:offset:length}, which in this case would be ${GIT_COMMIT:0:7}, i.e., you are missing a colon.

But:

  • git filter-branch is an sh (not bash) script;
  • the filter-branch script uses eval; and
  • this isn't really the best way to do this anyway.

The first two mean that you cannot rely on bash-specific syntax here.

The third means that there's no real reason to do so.

One option is to use the -o (or) operator of the [ (aka test) program:

if [ $GIT_COMMIT = ... -o
     $GIT_COMMIT = ... -o
     ...
     $GIT_COMMIT = ... ]; then

but that's still too painful, in my opinion. Instead, why not use grep, e.g.:

if echo $GIT_COMMIT | grep -F -f /tmp/list >/dev/null; then

where the file /tmp/list contains the IDs you want to skip? If grep finds one of them, it exits with a zero (success) status, otherwise it exits with a nonzero (failure) status, the same way the test or [ program exits 0 if the test succeeds, or nonzero if it fails.

(If your grep has -q, you can use that rather than redirecting to /dev/null.)

Comments