Mig82
Mig82

Reputation: 5480

How to reapply Git tags after rewriting history using rebase?

I have a project where I'd started working on my old laptop and then continued on my new one. On my old laptop I had properly configured Git with user.name and user.email, but when I switched to my new one I just cloned the repo and continued working without configuring user.name and user.email. As a result, the commits I made from my new laptop were attributed to my local machine's user for user.name and [computer_name]@local for user.email. I wanted to amend all the commits made from my new laptop and found the way to do it here.

git rebase -r <last commit from my old laptop> \
    --exec 'git commit --amend --no-edit --reset-author'

Note, I'm the only one working on this repo, so rewriting history was not a big problem. However, one thing that did go wrong was that all the tags and releases I had created in my Git repo where left orphaned. GitHub shows me a message next to each tag that reads something like this:

~"this tag points to a commit outside any of the repository branches, or points to a different repo"~.

This is because amending the commit's author changes its identifying hash. So the tags point to the hashes of the old commits before the rebase command.

So, need to re-apply each tag to the new commit that corresponds to the old one it's pointing to.

How do I do that?

Upvotes: 1

Views: 577

Answers (3)

Kronos2308
Kronos2308

Reputation: 1

In my case i have to remove files from repo with bfg and all the tags was messedup i use this code to rewrite the tags

git show-ref --tags | \
while read commit; do \
    date=$(git show -s --format=%ct  $commit)
    ncom=$(git log --oneline --since=$date --until=$date --format="%H")
    com=${commit:0:40}
    tag=${commit:51:8}
    if [ $ncom = $com ]; then
        se=""
    else
        echo ----
        echo $commit
        echo $ncom $tag
        git tag -f $tag $ncom
        echo ++++++++++++
        sleep 0.1
    fi;    
done
git push --tags -f

I take all the old tags commit hash and date, searh in the repo for a commit made at the same time, then assign the tag to the commit foud at the same date

Upvotes: 0

milahu
milahu

Reputation: 3589

to restore tags by author date

note: this does not handle annotated tags

#!/usr/bin/env bash

# fix git tags after rewriting the git history
# with "git-filter-repo" or "git rebase"

# what branches are allowed for tags?
# usually, tags are not allowed on backup branches
#branches="master branch2 branch3"
branches="master"

branches_regex="$(printf '|%s' $branches)"
branches_regex="(${branches_regex:1})"

echo "branches regex: $branches_regex"

while read tag_commit tag_ref; do
  tag_name=${tag_ref#*/*/}
  echo
  echo "checking tag $tag_name (commit $tag_commit) (date $tag_author_date)"
  tag_author_date=$(git log -1 --format='format:%ad' $tag_ref)
  tag_commit_branches="$(git branch --contains $tag_commit --format='%(refname)')"
  if ! echo "$tag_commit_branches" | grep -q -x -E "refs/heads/$branches_regex"; then
    echo "tag $tag_name is not part of branches $branches_regex"

    found=false
    for branch in $branches; do
      commit_candidates="$(git log $branch --format=format:"%ad %H" | grep "^$tag_author_date ")"
      if [[ -z "$commit_candidates" ]]; then
        #echo "error: found no commit candidates for tag $tag_name"
        :
      elif [[ $(echo "$commit_candidates" | wc -l) == 1 ]]; then
        commit_hash=${commit_candidates##* }
        echo "ok: found one commit candidate ($commit_hash) in branch $branch with author date $tag_author_date"
        echo git tag -f $tag_name $commit_hash
        git tag -f $tag_name $commit_hash
        found=true
        break
      else
        echo "error: found multiple commit candidates for tag $tag_name"
        echo "please fix this tag manually with one of these commands:"
        while read commit_candidate; do
          commit_hash=${commit_candidate##* }
          echo "  git tag -f $tag_name $commit_hash"
        done <<<"$commit_candidates"
      fi
    done
    if ! $found; then
      echo "error: found no commit candidates for tag $tag_name in branches $branches_regex"
    fi

  else
    tag_branches="$(echo "$tag_commit_branches" | grep -x -E "refs/heads/$branches_regex" | sed 's|^refs/heads/||')"
    echo "ok: tag $tag_name is part of branches" $tag_branches
  fi
done < <(
  git tag -l --format='%(objectname) %(refname)'
)

Upvotes: 0

Mig82
Mig82

Reputation: 5480

I managed to do this with a combination of rev-list, awk, log and tag.

git rev-list --all --tags | \
while read commit; do \
    new_commit=$(git log --format="%H" -n 1 $commit); \
    git tag -f $(git tag --contains $commit) $new_commit; \
done

Here's a breakdown of what it does:

  1. git rev-list --all --tags: Lists all the commits that are reachable from any reference (branches, tags) in the repository.
  2. while read commit; do ... done: Reads each commit ID from the output of git rev-list and executes the following commands for each commit.
  3. new_commit=$(git log --format="%H" -n 1 $commit): Retrieves the new commit ID corresponding to the old commit ID by using the %H format option, which represents the full commit hash.
  4. git tag -f $(git tag --contains $commit) $new_commit: Reapplies the tags that contain the old commit ID ($commit) to the corresponding new commit ID ($new_commit) using the git tag command with the -f (force) option. This overwrites any existing tags on the new commits with the same name.

With this, each old tag will be repositioned to point to the corresponding new commit ID.

P.S.: Full disclosure, I used an AI assistant to figure this out.

Upvotes: 0

Related Questions