Post

User email migration

User email migration

User email migration

The problem

You may find yourself in a situation where all of your users email addresses need to change. This can happen if your organization is acquired by another or if your organization decides to go through a total rename and rebranding exercise. How do you change all of your Gitlab users email addresses, and how, as Gitlab uses users email addresses to link them to their history, do you maintain that link after the address change.

The solution

Gitlab maintains the relationship with things such as commits using a users email address, a user can have multiple email addresses and we can use this to maintain that link to their history. We demote the users old email address to a secondary email address, this maintains their history, then add a new primary email address.

Migrating users email addresses

We can demote the email address to a secondary address and add the new address with a rake task.

The data

We need is some data, the users old email address and their new one in a csv file.

1
2
3
# /tmp/email_updates.csv
user1@example.com,user1@acme.com
user2@example.com,user2@acme.com

The script

Then a rake task that will read that data, find the user using their primary email address, demote the primary address to a secondary and add the new primary address.

Copy the rake task below to /opt/gitlab/embedded/service/gitlab-rails/lib/tasks/custom_user_task.rake

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
require 'csv'

namespace :custom do
  desc "Update user emails from CSV file"
  task :update_user_emails_from_csv, [:csv_file_path] => :environment do |t, args|
    csv_file_path = args[:csv_file_path]

    if csv_file_path.nil?
      puts "Usage: gitlab-rake custom:update_user_emails_from_csv[/path/to/file.csv]"
      puts "CSV format: old_email,new_email (one pair per line)"
      exit 1
    end

    unless File.exist?(csv_file_path)
      puts "Error: File '#{csv_file_path}' not found"
      exit 1
    end

    begin
      successful_updates = 0
      failed_updates = 0

      CSV.foreach(csv_file_path) do |row|
        old_email = row[0]&.strip
        new_email = row[1]&.strip

        if old_email.nil? || new_email.nil? || old_email.empty? || new_email.empty?
          puts "Skipping invalid row: #{row.inspect}"
          failed_updates += 1
          next
        end

        puts "\nProcessing: #{old_email} -> #{new_email}"

        user = User.find_by(email: old_email)

        if user
          puts "  Current email: #{user.email}"
          user.update!(email: new_email)
          user.confirm
          user.save!
          puts "  New email: #{user.email}"
          puts "  ✓ Successfully updated!"
          successful_updates += 1
        else
          puts "  ✗ User with email '#{old_email}' not found"
          failed_updates += 1
        end
      end

      puts "\n=== Summary ==="
      puts "Successfully updated: #{successful_updates}"
      puts "Failed updates: #{failed_updates}"

    rescue => e
      puts "Error processing CSV: #{e.message}"
    end
  end
end

And then run the task sudo gitlab-rake custom:update_user_emails_from_csv[/tmp/email_updates.csv]

What if a user deletes their old email

But what happens if the user accidentally deletes their secondary address, they can’t just add it back in because Gitlab will want them to verify it, and they probably don’t have access to that mail box anymore. We can fix that with another rake task to confirm that email address without using the link in the email.

1
2
3
4
sudo gitlab-rails console
user = User.find_by(email: '<email address>')
email = user.emails.find_by(email: '<email address>')
email.confirm
This post is licensed under CC BY 4.0 by the author.