I'm not gonna say it was pretty, actually from a design perspective it was disgusting, but from the perspective of handling a dozen versions of the same code in the same application, it was certainly very sane.
For example, at some point we supported versions 2.0, 3.0, 4.0, 4.1, 4.2, 4.3 and the freshly new 5.0. They inherited this way: 2.0 -> 3.0 -> 4.0 -> 4.1 -> 4.2 -> 4.3 -> 5.0, easy enough.
One day decide to deprecate version 2.0.
What happens is we consolidate 2.0 code in 3.0:
1. If a method calls super, copy + paste from 2.0 its place. 2. If it's overridden, there's nothing to do. 3. Run unit, regression tests, on the remainder of the versions and fix anything that might have been missed. 4. Delete 2.0 code.
The end.
If we need to fix a bug introduced in 4.1, you fix it there and it gets fixed automatically for the rest of the higher versions.