automatically rebasing an entire repo to restructure and apply Prettier

Issue

I want to clean up the history of a repo with a year’s worth of commits, about 4000 in all.

  • One contributor has consistently disagreed with formatting standards and has repeatedly changed the Prettier config file, or not used Prettier at all. As a result, the git history is a sort of tug-of-war of cosmetic changes with huge diffs, and the real changes are difficult to find.

  • The name of the front-end directory was renamed at some point. We load the project from within that directory, making it a nuisance to access earlier git history from VSCode.

  • A TypeScript transpiler was added at some point, generating a file.js and file.js.map for every file.ts across the project. The files aren’t generated consistently (sometimes they have a particular comment at the end, sometimes not), which adds to the noise in the git history.

My tentative plan is to rebase everything:

I’ll keep a backup of the repo just in case, and then rebase, doing the following on every commit:

  • Apply consistent Prettier settings;
  • Rename the front-end directory if necessary;
  • Delete all the unnecessary file.js and file.js.map files.

Our team would then move to the new repo.

Specifically:

GIT_SEQUENCE_EDIT=cat git rebase
  --strategy recursive --strategy-option theirs --rebase-merges \
  --exec '../cleanup.sh && git add . && git commit --amend --no-edit --no-verify --allow-empty' \
  e709bcd1

where e709bcd1 is the SHA of a good place to start, using the script cleanup.sh:

#! /usr/bin/env zsh
setopt nullglob

echo $(git rev-parse HEAD) > commit.log

# If both directories exist, assume old_front_end is the real one,
# so delete new_front_end to allow us to rename old_front_end.
# (Otherwise, `mv` will move the one directory into the other.)
if [[ -d "old_front_end" ]] && [[ -d "new_front_end" ]]; then
  rm -rf new_front_end
fi

# Rename old_front_end if necessary
if [[ -d "old_front_end" ]] && [[ ! -d "new_front_end" ]]; then
  mv old_front_end new_front_end
fi

if [[ -d "new_front_end" ]]; then
  # Clean up JS files
  for file in "new_front_end/src/**/*.ts"; do
    [[ ! -e $file ]] && continue  # skip following if no such file
    rm "${file%.*}.js"
    rm "${file%.*}.js.map"
  done

  # Apply consistent Prettier settings
  prettier --config ~/external_source_of_truth/.prettierrc -w "new_front_end/src/**/*.{js,ts,svelte,gql,css,scss}" || true
fi

Questions:

  • I don’t have much experience rebasing or writing shell scripts. Is this a reasonable plan? Will it have unfortunate consequences?
  • I’ve tried running the script, and it gets stuck fairly often with a merge conflict. It seems that I can always resolve the conflict simply by doing git add . && git rebase --continue, but I’d rather not have to do this hundreds of times. Can I automate this?

Solution

Answers to the two questions:

  • Seems reasonable enough.

  • Solved using git filter-repo and the associated tool lint-history:

Rename front-end directory:

git filter-repo --path-rename old_front_end/:new_front_end/

Clean up JS files:

git filter-repo --force --filename-callback '
  if filename.endswith(b".js"):
    ts_file = filename[:-3] + b".ts"
    if os.path.isfile(ts_file.decode("utf-8")):
      return None

  if filename.endswith(b".js.map"):
    ts_file = filename[:-7] + b".ts"
    if os.path.isfile(ts_file.decode("utf-8")):
      return None
    
  return filename
'

Prettier

lint-history --relevant '
  return filename.endswith(b".ts") or filename.endswith(b".js")
         or filename.endswith(b".svelte") or filename.endswith(b".css")
         or filename.endswith(b".scss") or filename.endswith(b".gql")
  ' --filenames-important maybe-prettier

I needed to write a helper script called maybe-prettier because some commits had bad files with unresolved merge symbols (<<<<<<<< HEAD and so on). Prettier was unable to format those files and exited with an error, halting the progress of filter-repo. To get around this, maybe-prettier first checks to see if a file is able to be formatted; it always exits with success.

maybe-prettier

#! /usr/bin/env zsh

# Prettier gives three exit codes:
#  0 file is good
#  1 needs formatting
#  2 error
prettier --config ~/external_source_of_truth/.prettierrc -c $1
if [[ $? = 1 ]]; then
  prettier --config ~/external_source_of_truth/.prettierrc -w $1
fi

Answered By – Théophile

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published