I wish to offer a solution in Ruby. Considering that Ruby and Python have many similarities most readers should be able to at least get a gist of what I am doing. I am posting this answer in the chance that a reader might look upon it favourably and post an equivalent, if not improved-upon, solution in Python.
First create a constant holding a regular expression.
RGX = /(.)\1/
This expression matches a pair of the same character (e.g., "aa"). . matches any character (other than a line terminator), which is saved to capture group 1. \1 matches the content of capture group 1.
Next I will define a constant H that holds a hash that maps pairs of letters into the "successor" of both, such as "aa"->"b" and "zz"->"a".
a = ('a'..'z').to_a
  #=> ["a", "b",..., "y", "z"]
H = a.map { |c| c*2 }.zip(a.rotate).to_h
  #=> {"aa"=>"b", "bb"=>"c",..., "yy"=>"z", "zz"=>"a"}
We may now define a method that takes as its argument the string and returns the desired string.
def doit(str)
  loop do
    break str if str.sub!(RGX, H).nil?
  end
end
Let's try it.
doit("barrsxffzzdggh")
  #=> "batxgadi"
It can be seen that value of str at each iteration is as follows.
bassxffzzdggh
batxffzzdggh
batxgzzdggh
batxgadggh
batxgadhh
batxgadi
I will now break down each step of the method.
First create an array containing the letters of the alphabet.
a = ('a'..'z').to_a
   #=> ["a", "b",..., "y", "z"]
'a'..'z' denotes a range and the method Range#to_a maps it into an array.
The hash H is constructed in four steps.
x = a.map { |c| c*2 }
  #=> ["aa", "bb", "cc",..., "yy", "zz"]
b = a.rotate
  #=> ["b", "c",..., "z", "a"]
c = x.zip(b)
  #=> [["aa", "b"], ["bb", "c"],..., ["yy", "z"], ["zz", "a"]]
H = c.to_h
  #=> {"aa"=>"b", "bb"=>"c",..., "yy"=>"z", "zz"=>"a"}
These steps use the methods Array#map, String#*, Array#rotate, Array#zip and Array#to_h.
Now assign the given string to the variable str.
str = "barrsxffzzdggh"
Ruby's method Kernel#loop more-or-less loops to its end statement  until the break keyword is encountered. The single statement within the loop is the following.
    str.sub!(RGX, H)
      #=> "bassxffzzdggh"
This uses the form of Ruby's method String#sub! which matches substrings with its first argument (a string or a regular expression), and uses its second argument, a hash, for making substitutions. This method modifies the string in place. If a replacement is made the string is returned; else nil is returned. (Ruby has a non-destructive counterpart to this methods, sub, and methods gsub! and gsub for making multiple replacements.)
Initially an attempt is made to match str with the regular expression RGX. It matches "rr". As H["rr"] #=> "s", "s" is substituted for "rr" and the modified string is returned (as well as changed in place), so we repeat the loop. This continues until sub! returns nil, at which time we are finished, so we break out of the loop and return the current contents of str.