{"thread":{"id":"49683","subject":"git filter-branch --filter-renames ?","startedAt":"2018-10-25T21:46:41Z","lastAt":"2018-10-25T21:46:41Z","messageCount":1,"participants":["Jason Cooper"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"361565","messageId":"20181025213032.GD24185@io.lakedaemon.net","threadId":"49683","inReplyTo":null,"subject":"git filter-branch --filter-renames ?","fromName":"Jason Cooper","fromEmail":"git@lakedaemon.net","sentAt":"2018-10-25T21:30:32Z","receivedAt":"2018-10-25T21:46:41Z","isPatch":false,"sender":{"key":"git@lakedaemon.net","avatar":null},"body":"All,\n\nI recently needed to extract the git history of a portion of an existing\nrepository.  My initial attempts using --subdirectory-filter, subtrees,\netc weren't as successful as I'd hoped.\n\nThe primary reason for my failures were due to the fact that this\nparticular source repository has seen a lot of code movement and renames\nin-place.  As a result, filters such as subdirectory filter failed to\nkeep commits prior to the renames.\n\nSo, long story short, I've attached below a hacked together script (yes,\nit's sad when one writes a script to call a script :-/) that solves the\nproblem for me.\n\nMy hope is that some other poor sob in my position discovers this\nscript, uses it and moves on.  If enough people think it's useful\ndespite the cornercases [1], I'd be happy to work on integrating it into\nfilter-branch.\n\nthx,\n\nJason.\n\n[1] Namely that if two different files held the same full-path name at\ndifferent times in the source repo, you'll get some errant commits in\nthe history.\n\n------------------->8--------------------------------------------------\n#!/bin/bash\n#\n# git-filter-renames: Similar to --subdirectory-filter but tracks renames\n#\n# Basic use:\n#  $ git clone path/to/source_repo dest_repo\n#  $ cd dest_repo\n#  $ git tags | xargs git tag -d # ours are signed, so would fail to verify\n#  $ git remote remove origin\n#  $ git gc --aggressive --prune=now --force\n#  $ git fsck\n#  $ git-filter-renames.sh \"[PREFIX] \" fileA subdirB/ fileC subdirD/subdirE ...\n#  $ rm -rf .git/refs/original\n#  $ git gc --aggressive --prune=now --force\n#  $ git fsck\n\nDEBUG=1\n\nif [ $# -le 1 ]; then\n\techo >&2 \"Usage:\"\n\techo >&2 \"    ${0##*/} '[subj prefix] ' fileA fileB dir1 sub/dir2\"\n\techo >&2 \"\"\n\texit 1\nfi\n\nif [ $DEBUG == 1 ]; then\n\trm -rf /tmp/git-filter-renames-*\nfi\n\nTMP_DIR=\"`mktemp -d /tmp/git-filter-renames-XXXXXX`\"\n\nPREFIX=\"${1}\"\nshift\n\n# take in the list of files to preserve\n# note: directories are recursed\necho -n \"\" >$TMP_DIR/user_list.txt\nfor arg in $*; do\n\tif [ -d \"$arg\" ]; then\n\t\tfind $arg -type f >>$TMP_DIR/user_list.txt\n\telif [ -f \"$arg\" ]; then\n\t\techo \"$arg\" >>$TMP_DIR/user_list.txt\n\telse\n\t\techo >&2 \"What the hell is '$arg'?\"\n\tfi\ndone\n\necho -n \"\" >$TMP_DIR/trace_list.txt\nwhile read fn <&4; do\n\twhile read ofn <&5; do\n\t\techo \"^$ofn\\$\"\n\tdone 5< <(git log --format=format: --follow --name-only -- \"$fn\" | \\\n\t\t  sed -e '/^$/d' | sort -u)\ndone 4< <(cat $TMP_DIR/user_list.txt) | sort -u >>$TMP_DIR/trace_list.txt\n\n# stage the filter script\ncat >$TMP_DIR/filter.sh <<EOF\ngit ls-files | \\\\\n\tgrep -vf $TMP_DIR/trace_list.txt | \\\\\n\txargs -r git rm -qrf --ignore-unmatch\nEOF\nchmod +x $TMP_DIR/filter.sh\n\n# stage the msg filter script\ncat >$TMP_DIR/msg_filter.sh <<EOF\nsed -e \"1 s/^/$PREFIX/\"\nEOF\nchmod +x $TMP_DIR/msg_filter.sh\n\n# do the filtering\necho >&2 \"Doing filtering\"\ngit filter-branch --prune-empty -f --index-filter \"$TMP_DIR/filter.sh\" \\\n\t--msg-filter \"$TMP_DIR/msg_filter.sh\" \\\n\tHEAD\n# cleanup\nif [ $DEBUG == 0 ]; then\n\trm -rf $TMP_DIR\nfi\n"}]}