Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save the0neWhoKnocks/5438de48057c5eff03afcbac095f217c to your computer and use it in GitHub Desktop.
Save the0neWhoKnocks/5438de48057c5eff03afcbac095f217c to your computer and use it in GitHub Desktop.
How to convert author info in multiple GitHub repos

How to Convert Author Info in Multiple GitHub Repos

This came about because I saw how easy it was to view my info (name and email) by just adding .patch at the end of a commit URL on GitHub.


  1. Go to https://github.com/settings/emails to view your current mappings for emails. For each email there'll be a Not visible in emails section. Take note of the sentence that calls out We will instead use followed by an email. That users.noreply.github.com email is the one that's going to be used going forward.

    • Also make sure these items are checked
      [X] Keep my email addresses private
      [X] Block command line pushes that expose my email
      
  2. First update your local credentials

    git config --global user.name "<NEW_NAME>"
    git config --global user.email "<NEW_EMAIL>"

    or just directly edit ~/.gitconfig

    For my particular case though, I only wanted to update credentials for GitHub and not my repos hosted internally. That's where includeIf comes in (you have to have at least git v2.36).

    Open .gitconfig

    [user]
      email = <OLD_EMAIL>
      name = <OLD_NAME>
    + 
    + [includeIf "hasconfig:remote.*.url:*github.com:*/**"]
    +   path = ~/.gitconfig.github
    + 
    + [includeIf "hasconfig:remote.*.url:*gist.github.com*"]
    +   path = ~/.gitconfig.github

    Create a new file ~/.gitconfig.github, and add

    [user]
      email = <NEW_EMAIL>
      name = <NEW_NAME>

    To verify run:

    git config --get user.email && git config --get user.name
  3. If you already have a GitHub token, you can skip this step. Go to https://github.com/settings/tokens?type=beta (to create a Fine Grained Token) and click Generate Token.

    Token name: Change Repo Author
    Expiration: (chose Custom, and set it to last a day)
    Repository access: All repositories
    Permissions: Metadata [Read-Only]
    
    (click Generate token)
    
    (copy and store the token)
    
  4. Create a tmp folder, add the scripts in this gist to it, cd into tmp and run chmod +x *.sh.

  5. Run GH_TOKEN="<YOUR_TOKEN>" ./scrape-urls.sh. When it's done scraping, it'll print the number of URLs it found, make sure that matches what's in your repo (public & private combined).

  6. Since git recommends git-filter-repo instead of it's own filter-branch install it with sudo apt install git-filter-repo. More install instructions can be found on https://github.com/newren/git-filter-repo/blob/main/INSTALL.md#simple-installation or https://andrewlock.net/rewriting-git-history-simply-with-git-filter-repo/#installing-git-filter-repo-using-docker.

  7. Create a .mailmap within the tmp folder. It's format looks like this:

    NEW_NAME <NEW_EMAIL> OLD_NAME <OLD_EMAIL>
    

    So an example of remapping multiple items would be:

    Nox <[email protected]> John Doe <[email protected]>
    Nox <[email protected]> jdoe <[email protected]>
    

    Note that .mailmap items take precedence over what you put in your .gitconfig, but it needed to be updated so that future commits have the correct info.

  8. Run ./update-author.sh.

    • Few things to note:
      • If you want to test things before pulling the trigger
        - # git-filter-repo --dry-run --mailmap "../.mailmap"
        - git-filter-repo --quiet --mailmap "../.mailmap"
        + git-filter-repo --dry-run --mailmap "../.mailmap"
        + # git-filter-repo --quiet --mailmap "../.mailmap"
        ...
        - git push --force --all origin
        - git push --force --tags origin
        + # git push --force --all origin
        + # git push --force --tags origin
      • If something goes wrong and you want to undo the changes, go into repos.bak/<REPO_NAME> and run
        git push --force --all origin
        git push --force --tags origin
#!/bin/bash
set -e
urlsFilePath="./repo-urls.conf"
function getRepoURLs {
[[ "$1" != "" ]] && after=", after: \\\"$1\\\"" || after=""
curlResp=$(curl \
-H "Authorization: bearer $GH_TOKEN" \
-X POST \
-d "{ \"query\": \" \
query { \
viewer { \
repositories(first: 30$after) { \
totalCount \
pageInfo { endCursor hasNextPage } \
nodes { sshUrl } \
} \
} \
} \
\" }" \
"https://api.github.com/graphql" \
)
if [[ "$curlResp" == *'"errors":'* ]]; then
printf "\n$curlResp\n"
exit
fi
hasNextPage=$(echo "$curlResp" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['viewer']['repositories']['pageInfo']['hasNextPage'])")
pyScript=(
"import sys, json;"
"stdin = json.load(sys.stdin);"
"for item in stdin['data']['viewer']['repositories']['nodes']:"
" print(item['sshUrl']);"
)
pyScript=$(printf "%s\n" "${pyScript[@]}")
sshURLs=$(echo "$curlResp" | python3 -c "$pyScript")
echo "$sshURLs" >> "$urlsFilePath"
if [[ "$hasNextPage" == "True" ]]; then
endCursor=$(echo "$curlResp" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['viewer']['repositories']['pageInfo']['endCursor'])")
getRepoURLs "$endCursor"
else
printf "\n-------------\n\n [DONE] Found '$(cat "$urlsFilePath" | sed '/^\s*$/d' | wc -l)' repo URLs\n"
fi
}
if [ -f "$urlsFilePath" ]; then rm "$urlsFilePath"; fi
getRepoURLs
#!/bin/bash
set -e
urlsFilePath="./repo-urls.conf"
backupPath="./repos.bak"
mkdir -p "$backupPath"
while read url; do
if [[ "$url" == "" ]]; then continue; fi # in case the User adds in some blank lines.
echo "-----------------------------------------------------------------------"
repoName=$(echo "$url" | sed -nr 's|.*/(.*)\.git$|\1|p')
if [[ "$repoName" == "" ]]; then
printf "\n [ERROR] There was a problem parsing the repo name for:\n \"$url\"\n"
exit
fi
if [[ "$url" == "#"* ]]; then
echo "[SKIP] $repoName"
else
echo "[START] Processing: \"$repoName\""
if [ ! -d "$repoName" ]; then
echo " - [CLONE] $url"
git clone -q "$url"
echo " - [PULL] Down all remote branches"
(cd "$repoName" && git pull --all)
else
echo " - [SKIPPED:CLONE] Repo already cloned"
fi
if [ ! -d "$backupPath/$repoName" ]; then
echo " - [BACKUP] Fully clone the repo"
cp -r "$repoName" "$backupPath/"
else
echo " - [SKIPPED:BACKUP] Repo already backed up"
fi
(
cd "$repoName"
if [ ! -f "./.git/filter-repo/already_ran" ]; then
authorEmail=$(git config --get user.email)
authorName=$(git config --get user.name)
printf " - [UPDATE] Author to:\n Name: $authorName\n Email: $authorEmail\n"
# git-filter-repo --dry-run --mailmap "../.mailmap"
git-filter-repo --quiet --mailmap "../.mailmap"
else
echo " - [SKIPPED:UPDATE] Filtering already ran on this repo"
fi
issues=$(cat "./.git/filter-repo/suboptimal-issues")
if \
echo "$issues" | grep -q "No filtering problems" \
|| echo "$issues" | grep -q "was left as-is in any commit messages" \
; then
echo " - [SUCCESS] No issues detected"
echo " - [COPY] Old git config to updated repo"
cp -f "../$backupPath/$repoName/.git/config" "./.git/"
echo " - [PUSH] Local changes to Remote"
git push --force --all origin
git push --force --tags origin
else
printf " - [ERROR] Issue detected, from \".git/filter-repo/suboptimal-issues\":\n\n$issues\n"
exit 77
fi
)
[ "$?" -eq 77 ] && exit
fi
done <"$urlsFilePath"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment