diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index e3e140ea35ec0f209ff42bd3380a68db892a689a..fded391cc849b799a6aff7065ef610bab165d3f8 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -2,7 +2,14 @@
 /* eslint-disable vue/no-v-html */
 import { escape } from 'lodash';
 import { mapActions, mapGetters } from 'vuex';
-import { GlDeprecatedButton, GlTooltipDirective, GlLoadingIcon, GlIcon } from '@gitlab/ui';
+import {
+  GlDeprecatedButton,
+  GlTooltipDirective,
+  GlSafeHtmlDirective,
+  GlLoadingIcon,
+  GlIcon,
+  GlButton,
+} from '@gitlab/ui';
 import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
 import FileIcon from '~/vue_shared/components/file_icon.vue';
 import { truncateSha } from '~/lib/utils/text_utility';
@@ -21,9 +28,11 @@ export default {
     GlIcon,
     FileIcon,
     DiffStats,
+    GlButton,
   },
   directives: {
     GlTooltip: GlTooltipDirective,
+    SafeHtml: GlSafeHtmlDirective,
   },
   props: {
     discussionPath: {
@@ -77,6 +86,21 @@ export default {
 
       return this.discussionPath;
     },
+    submoduleDiffCompareLinkText() {
+      if (this.diffFile.submodule_compare) {
+        const truncatedOldSha = escape(truncateSha(this.diffFile.submodule_compare.old_sha));
+        const truncatedNewSha = escape(truncateSha(this.diffFile.submodule_compare.new_sha));
+        return sprintf(
+          s__('Compare %{oldCommitId}...%{newCommitId}'),
+          {
+            oldCommitId: `<span class="commit-sha">${truncatedOldSha}</span>`,
+            newCommitId: `<span class="commit-sha">${truncatedNewSha}</span>`,
+          },
+          false,
+        );
+      }
+      return null;
+    },
     filePath() {
       if (this.diffFile.submodule) {
         return `${this.diffFile.file_path} @ ${truncateSha(this.diffFile.blob.id)}`;
@@ -311,5 +335,18 @@ export default {
         </a>
       </div>
     </div>
+
+    <div
+      v-if="diffFile.submodule_compare"
+      class="file-actions d-none d-sm-flex align-items-center flex-wrap"
+    >
+      <gl-button
+        v-gl-tooltip.hover
+        v-safe-html="submoduleDiffCompareLinkText"
+        class="submodule-compare"
+        :title="s__('Compare submodule commit revisions')"
+        :href="diffFile.submodule_compare.url"
+      />
+    </div>
   </div>
 </template>
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 3b25de521d023a170acf2ffc331c8207bda63541..7c254e069f67f38b26fc1a5f249ee5da78c07724 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -100,20 +100,43 @@ def parallel_diff_btn
   end
 
   def submodule_link(blob, ref, repository = @repository)
-    project_url, tree_url = submodule_links(blob, ref, repository)
-    commit_id = if tree_url.nil?
-                  Commit.truncate_sha(blob.id)
-                else
-                  link_to Commit.truncate_sha(blob.id), tree_url
-                end
+    urls = submodule_links(blob, ref, repository)
+
+    folder_name = truncate(blob.name, length: 40)
+    folder_name = link_to(folder_name, urls.web) if urls&.web
+
+    commit_id = Commit.truncate_sha(blob.id)
+    commit_id = link_to(commit_id, urls.tree) if urls&.tree
 
     [
-      content_tag(:span, link_to(truncate(blob.name, length: 40), project_url)),
+      content_tag(:span, folder_name),
       '@',
       content_tag(:span, commit_id, class: 'commit-sha')
     ].join(' ').html_safe
   end
 
+  def submodule_diff_compare_link(diff_file)
+    compare_url = submodule_links(diff_file.blob, diff_file.content_sha, diff_file.repository, diff_file)&.compare
+
+    link = ""
+
+    if compare_url
+
+      link_text = [
+          _('Compare'),
+          ' ',
+          content_tag(:span, Commit.truncate_sha(diff_file.old_blob.id), class: 'commit-sha'),
+          '...',
+          content_tag(:span, Commit.truncate_sha(diff_file.blob.id), class: 'commit-sha')
+        ].join('').html_safe
+
+      tooltip = _('Compare submodule commit revisions')
+      link = content_tag(:span, link_to(link_text, compare_url, class: 'btn has-tooltip', title: tooltip), class: 'submodule-compare')
+    end
+
+    link
+  end
+
   def diff_file_blob_raw_url(diff_file, only_path: false)
     project_raw_url(@project, tree_join(diff_file.content_sha, diff_file.file_path), only_path: only_path)
   end
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index 2e7146eaa43c871f7251208cb54951c4eecb7665..959178c47d7c8050fcc062f03378c0e17a284076 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -6,12 +6,12 @@ module SubmoduleHelper
   VALID_SUBMODULE_PROTOCOLS = %w[http https git ssh].freeze
 
   # links to files listing for submodule if submodule is a project on this server
-  def submodule_links(submodule_item, ref = nil, repository = @repository)
-    repository.submodule_links.for(submodule_item, ref)
+  def submodule_links(submodule_item, ref = nil, repository = @repository, diff_file = nil)
+    repository.submodule_links.for(submodule_item, ref, diff_file)
   end
 
-  def submodule_links_for_url(submodule_item_id, url, repository)
-    return [nil, nil] unless url
+  def submodule_links_for_url(submodule_item_id, url, repository, old_submodule_item_id = nil)
+    return [nil, nil, nil] unless url
 
     if url == '.' || url == './'
       url = File.join(Gitlab.config.gitlab.url, repository.project.full_path)
@@ -34,21 +34,24 @@ def submodule_links_for_url(submodule_item_id, url, repository)
       project.sub!(/\.git\z/, '')
 
       if self_url?(url, namespace, project)
-        [url_helpers.namespace_project_path(namespace, project),
-         url_helpers.namespace_project_tree_path(namespace, project, submodule_item_id)]
+        [
+          url_helpers.namespace_project_path(namespace, project),
+          url_helpers.namespace_project_tree_path(namespace, project, submodule_item_id),
+          (url_helpers.namespace_project_compare_path(namespace, project, to: submodule_item_id, from: old_submodule_item_id) if old_submodule_item_id)
+        ]
       elsif relative_self_url?(url)
-        relative_self_links(url, submodule_item_id, repository.project)
+        relative_self_links(url, submodule_item_id, old_submodule_item_id, repository.project)
       elsif gist_github_dot_com_url?(url)
         gist_github_com_tree_links(namespace, project, submodule_item_id)
       elsif github_dot_com_url?(url)
-        github_com_tree_links(namespace, project, submodule_item_id)
+        github_com_tree_links(namespace, project, submodule_item_id, old_submodule_item_id)
       elsif gitlab_dot_com_url?(url)
-        gitlab_com_tree_links(namespace, project, submodule_item_id)
+        gitlab_com_tree_links(namespace, project, submodule_item_id, old_submodule_item_id)
       else
-        [sanitize_submodule_url(url), nil]
+        [sanitize_submodule_url(url), nil, nil]
       end
     else
-      [sanitize_submodule_url(url), nil]
+      [sanitize_submodule_url(url), nil, nil]
     end
   end
 
@@ -79,22 +82,30 @@ def relative_self_url?(url)
     url.start_with?('../', './')
   end
 
-  def gitlab_com_tree_links(namespace, project, commit)
+  def gitlab_com_tree_links(namespace, project, commit, old_commit)
     base = ['https://gitlab.com/', namespace, '/', project].join('')
-    [base, [base, '/-/tree/', commit].join('')]
+    [
+      base,
+      [base, '/-/tree/', commit].join(''),
+      ([base, '/-/compare/', old_commit, '...', commit].join('') if old_commit)
+    ]
   end
 
   def gist_github_com_tree_links(namespace, project, commit)
     base = ['https://gist.github.com/', namespace, '/', project].join('')
-    [base, [base, commit].join('/')]
+    [base, [base, commit].join('/'), nil]
   end
 
-  def github_com_tree_links(namespace, project, commit)
+  def github_com_tree_links(namespace, project, commit, old_commit)
     base = ['https://github.com/', namespace, '/', project].join('')
-    [base, [base, '/tree/', commit].join('')]
+    [
+      base,
+      [base, '/tree/', commit].join(''),
+      ([base, '/compare/', old_commit, '...', commit].join('') if old_commit)
+    ]
   end
 
-  def relative_self_links(relative_path, commit, project)
+  def relative_self_links(relative_path, commit, old_commit, project)
     relative_path = relative_path.rstrip
     absolute_project_path = "/" + project.full_path
 
@@ -107,7 +118,7 @@ def relative_self_links(relative_path, commit, project)
     target_namespace_path = File.dirname(submodule_project_path)
 
     if target_namespace_path == '/' || target_namespace_path.start_with?(absolute_project_path)
-      return [nil, nil]
+      return [nil, nil, nil]
     end
 
     target_namespace_path.sub!(%r{^/}, '')
@@ -116,10 +127,11 @@ def relative_self_links(relative_path, commit, project)
     begin
       [
         url_helpers.namespace_project_path(target_namespace_path, submodule_base),
-        url_helpers.namespace_project_tree_path(target_namespace_path, submodule_base, commit)
+        url_helpers.namespace_project_tree_path(target_namespace_path, submodule_base, commit),
+        (url_helpers.namespace_project_compare_path(target_namespace_path, submodule_base, to: commit, from: old_commit) if old_commit)
       ]
     rescue ActionController::UrlGenerationError
-      [nil, nil]
+      [nil, nil, nil]
     end
   end
 
diff --git a/app/serializers/diff_file_base_entity.rb b/app/serializers/diff_file_base_entity.rb
index 2af14f1eb82abf4ddc668b0e855e1a3c01491c5b..9f27191c3c8bca4353c379439ec36b3da19c4229 100644
--- a/app/serializers/diff_file_base_entity.rb
+++ b/app/serializers/diff_file_base_entity.rb
@@ -12,11 +12,23 @@ class DiffFileBaseEntity < Grape::Entity
   expose :submodule?, as: :submodule
 
   expose :submodule_link do |diff_file, options|
-    memoized_submodule_links(diff_file, options).first
+    memoized_submodule_links(diff_file, options)&.web
   end
 
   expose :submodule_tree_url do |diff_file|
-    memoized_submodule_links(diff_file, options).last
+    memoized_submodule_links(diff_file, options)&.tree
+  end
+
+  expose :submodule_compare do |diff_file|
+    url = memoized_submodule_links(diff_file, options)&.compare
+
+    next unless url
+
+    {
+      url: url,
+      old_sha: diff_file.old_blob&.id,
+      new_sha: diff_file.blob&.id
+    }
   end
 
   expose :edit_path, if: -> (_, options) { options[:merge_request] } do |diff_file|
@@ -96,11 +108,9 @@ class DiffFileBaseEntity < Grape::Entity
 
   def memoized_submodule_links(diff_file, options)
     strong_memoize(:submodule_links) do
-      if diff_file.submodule?
-        options[:submodule_links].for(diff_file.blob, diff_file.content_sha)
-      else
-        []
-      end
+      next unless diff_file.submodule?
+
+      options[:submodule_links].for(diff_file.blob, diff_file.content_sha, diff_file)
     end
   end
 
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index bd023e0442c13224f8ad4c4e746fed89e80d3824..187ebcb739cf5593c10d42364f3ed0e1dcc9a1af 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -9,6 +9,10 @@
     .file-header-content
       = render "projects/diffs/file_header", diff_file: diff_file, url: "##{file_hash}"
 
+    - if diff_file.submodule?
+      .file-actions.d-none.d-sm-block
+        = submodule_diff_compare_link(diff_file)
+
     - unless diff_file.submodule?
       - blob = diff_file.blob
       .file-actions.d-none.d-sm-block
diff --git a/changelogs/unreleased/compare-link-git-submodule-update.yml b/changelogs/unreleased/compare-link-git-submodule-update.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8632c095c5fe8cf101e66a57a9e6b105d45f22e5
--- /dev/null
+++ b/changelogs/unreleased/compare-link-git-submodule-update.yml
@@ -0,0 +1,5 @@
+---
+title: Add link to compare changes intoduced by a git submodule update
+merge_request: 37740
+author: Daniel Seemer @Phaiax
+type: added
diff --git a/lib/gitlab/graphql/representation/submodule_tree_entry.rb b/lib/gitlab/graphql/representation/submodule_tree_entry.rb
index 8d17cb9eecc4c4122915928dae97fc03cd6a3d97..aa5e74cc837c9ce90028bebf1dc0074865a63dc5 100644
--- a/lib/gitlab/graphql/representation/submodule_tree_entry.rb
+++ b/lib/gitlab/graphql/representation/submodule_tree_entry.rb
@@ -24,11 +24,11 @@ def initialize(submodule, submodule_links)
         end
 
         def web_url
-          @submodule_links.first
+          @submodule_links&.web
         end
 
         def tree_url
-          @submodule_links.last
+          @submodule_links&.tree
         end
       end
     end
diff --git a/lib/gitlab/submodule_links.rb b/lib/gitlab/submodule_links.rb
index b0ee0877f304a6f2b7f81e9dc339fadc2b69cb7e..38b10c5892da635ff5774b501ab58a7a6fa87a4b 100644
--- a/lib/gitlab/submodule_links.rb
+++ b/lib/gitlab/submodule_links.rb
@@ -4,14 +4,18 @@ module Gitlab
   class SubmoduleLinks
     include Gitlab::Utils::StrongMemoize
 
+    Urls = Struct.new(:web, :tree, :compare)
+
     def initialize(repository)
       @repository = repository
       @cache_store = {}
     end
 
-    def for(submodule, sha)
+    def for(submodule, sha, diff_file = nil)
       submodule_url = submodule_url_for(sha, submodule.path)
-      SubmoduleHelper.submodule_links_for_url(submodule.id, submodule_url, repository)
+      old_submodule_id = old_submodule_id(submodule_url, diff_file)
+      urls = SubmoduleHelper.submodule_links_for_url(submodule.id, submodule_url, repository, old_submodule_id)
+      Urls.new(*urls) if urls.any?
     end
 
     private
@@ -29,5 +33,15 @@ def submodule_url_for(sha, path)
       urls = submodule_urls_for(sha)
       urls && urls[path]
     end
+
+    def old_submodule_id(submodule_url, diff_file)
+      return unless diff_file&.old_blob && diff_file&.old_content_sha
+
+      # if the submodule url has changed from old_sha to sha, a compare link does not make sense
+      #
+      old_submodule_url = submodule_url_for(diff_file.old_content_sha, diff_file.old_blob.path)
+
+      diff_file.old_blob.id if old_submodule_url == submodule_url
+    end
   end
 end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 25268ad1792870c6c1494d858973ccc72e34b8ef..b805f1426328c4f62ace25e5440245ac055eaa3f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -6346,6 +6346,9 @@ msgstr ""
 msgid "Compare"
 msgstr ""
 
+msgid "Compare %{oldCommitId}...%{newCommitId}"
+msgstr ""
+
 msgid "Compare Git revisions"
 msgstr ""
 
@@ -6361,6 +6364,9 @@ msgstr ""
 msgid "Compare changes with the merge request target branch"
 msgstr ""
 
+msgid "Compare submodule commit revisions"
+msgstr ""
+
 msgid "Compare with previous version"
 msgstr ""
 
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index 426bca2ced2d03995ac922f291db094003891b5d..a419b6b9c8422275b271d2a67aea6b39dc9c89a9 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -24,7 +24,11 @@
         allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(22) # set this just to be sure
         allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix))
         stub_url([config.ssh_user, '@', config.host, ':gitlab-org/gitlab-foss.git'].join(''))
-        expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-foss'), namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash')])
+        aggregate_failures do
+          expect(subject.web).to eq(namespace_project_path('gitlab-org', 'gitlab-foss'))
+          expect(subject.tree).to eq(namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash'))
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'detects ssh on standard port without a username' do
@@ -32,14 +36,22 @@
         allow(Gitlab.config.gitlab_shell).to receive(:ssh_user).and_return('')
         allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix))
         stub_url([config.host, ':gitlab-org/gitlab-foss.git'].join(''))
-        expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-foss'), namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash')])
+        aggregate_failures do
+          expect(subject.web).to eq(namespace_project_path('gitlab-org', 'gitlab-foss'))
+          expect(subject.tree).to eq(namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash'))
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'detects ssh on non-standard port' do
         allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(2222)
         allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix))
         stub_url(['ssh://', config.ssh_user, '@', config.host, ':2222/gitlab-org/gitlab-foss.git'].join(''))
-        expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-foss'), namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash')])
+        aggregate_failures do
+          expect(subject.web).to eq(namespace_project_path('gitlab-org', 'gitlab-foss'))
+          expect(subject.tree).to eq(namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash'))
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'detects ssh on non-standard port without a username' do
@@ -47,21 +59,33 @@
         allow(Gitlab.config.gitlab_shell).to receive(:ssh_user).and_return('')
         allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix))
         stub_url(['ssh://', config.host, ':2222/gitlab-org/gitlab-foss.git'].join(''))
-        expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-foss'), namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash')])
+        aggregate_failures do
+          expect(subject.web).to eq(namespace_project_path('gitlab-org', 'gitlab-foss'))
+          expect(subject.tree).to eq(namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash'))
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'detects http on standard port' do
         allow(Gitlab.config.gitlab).to receive(:port).and_return(80)
         allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
         stub_url(['http://', config.host, '/gitlab-org/gitlab-foss.git'].join(''))
-        expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-foss'), namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash')])
+        aggregate_failures do
+          expect(subject.web).to eq(namespace_project_path('gitlab-org', 'gitlab-foss'))
+          expect(subject.tree).to eq(namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash'))
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'detects http on non-standard port' do
         allow(Gitlab.config.gitlab).to receive(:port).and_return(3000)
         allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
         stub_url(['http://', config.host, ':3000/gitlab-org/gitlab-foss.git'].join(''))
-        expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-foss'), namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash')])
+        aggregate_failures do
+          expect(subject.web).to eq(namespace_project_path('gitlab-org', 'gitlab-foss'))
+          expect(subject.tree).to eq(namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash'))
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'works with relative_url_root' do
@@ -69,7 +93,11 @@
         allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab/root')
         allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
         stub_url(['http://', config.host, '/gitlab/root/gitlab-org/gitlab-foss.git'].join(''))
-        expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-foss'), namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash')])
+        aggregate_failures do
+          expect(subject.web).to eq(namespace_project_path('gitlab-org', 'gitlab-foss'))
+          expect(subject.tree).to eq(namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash'))
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'works with subgroups' do
@@ -77,61 +105,105 @@
         allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab/root')
         allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
         stub_url(['http://', config.host, '/gitlab/root/gitlab-org/sub/gitlab-foss.git'].join(''))
-        expect(subject).to eq([namespace_project_path('gitlab-org/sub', 'gitlab-foss'), namespace_project_tree_path('gitlab-org/sub', 'gitlab-foss', 'hash')])
+        aggregate_failures do
+          expect(subject.web).to eq(namespace_project_path('gitlab-org/sub', 'gitlab-foss'))
+          expect(subject.tree).to eq(namespace_project_tree_path('gitlab-org/sub', 'gitlab-foss', 'hash'))
+          expect(subject.compare).to be_nil
+        end
       end
     end
 
     context 'submodule on gist.github.com' do
       it 'detects ssh' do
         stub_url('git@gist.github.com:gitlab-org/gitlab-foss.git')
-        is_expected.to eq(['https://gist.github.com/gitlab-org/gitlab-foss', 'https://gist.github.com/gitlab-org/gitlab-foss/hash'])
+        aggregate_failures do
+          expect(subject.web).to eq('https://gist.github.com/gitlab-org/gitlab-foss')
+          expect(subject.tree).to eq('https://gist.github.com/gitlab-org/gitlab-foss/hash')
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'detects http' do
         stub_url('http://gist.github.com/gitlab-org/gitlab-foss.git')
-        is_expected.to eq(['https://gist.github.com/gitlab-org/gitlab-foss', 'https://gist.github.com/gitlab-org/gitlab-foss/hash'])
+        aggregate_failures do
+          expect(subject.web).to eq('https://gist.github.com/gitlab-org/gitlab-foss')
+          expect(subject.tree).to eq('https://gist.github.com/gitlab-org/gitlab-foss/hash')
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'detects https' do
         stub_url('https://gist.github.com/gitlab-org/gitlab-foss.git')
-        is_expected.to eq(['https://gist.github.com/gitlab-org/gitlab-foss', 'https://gist.github.com/gitlab-org/gitlab-foss/hash'])
+        aggregate_failures do
+          expect(subject.web).to eq('https://gist.github.com/gitlab-org/gitlab-foss')
+          expect(subject.tree).to eq('https://gist.github.com/gitlab-org/gitlab-foss/hash')
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'handles urls with no .git on the end' do
         stub_url('http://gist.github.com/gitlab-org/gitlab-foss')
-        is_expected.to eq(['https://gist.github.com/gitlab-org/gitlab-foss', 'https://gist.github.com/gitlab-org/gitlab-foss/hash'])
+        aggregate_failures do
+          expect(subject.web).to eq('https://gist.github.com/gitlab-org/gitlab-foss')
+          expect(subject.tree).to eq('https://gist.github.com/gitlab-org/gitlab-foss/hash')
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'returns original with non-standard url' do
         stub_url('http://gist.github.com/another/gitlab-org/gitlab-foss.git')
-        is_expected.to eq([repo.submodule_url_for, nil])
+        aggregate_failures do
+          expect(subject.web).to eq(repo.submodule_url_for)
+          expect(subject.tree).to be_nil
+          expect(subject.compare).to be_nil
+        end
       end
     end
 
     context 'submodule on github.com' do
       it 'detects ssh' do
         stub_url('git@github.com:gitlab-org/gitlab-foss.git')
-        expect(subject).to eq(['https://github.com/gitlab-org/gitlab-foss', 'https://github.com/gitlab-org/gitlab-foss/tree/hash'])
+        aggregate_failures do
+          expect(subject.web).to eq('https://github.com/gitlab-org/gitlab-foss')
+          expect(subject.tree).to eq('https://github.com/gitlab-org/gitlab-foss/tree/hash')
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'detects http' do
         stub_url('http://github.com/gitlab-org/gitlab-foss.git')
-        expect(subject).to eq(['https://github.com/gitlab-org/gitlab-foss', 'https://github.com/gitlab-org/gitlab-foss/tree/hash'])
+        aggregate_failures do
+          expect(subject.web).to eq('https://github.com/gitlab-org/gitlab-foss')
+          expect(subject.tree).to eq('https://github.com/gitlab-org/gitlab-foss/tree/hash')
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'detects https' do
         stub_url('https://github.com/gitlab-org/gitlab-foss.git')
-        expect(subject).to eq(['https://github.com/gitlab-org/gitlab-foss', 'https://github.com/gitlab-org/gitlab-foss/tree/hash'])
+        aggregate_failures do
+          expect(subject.web).to eq('https://github.com/gitlab-org/gitlab-foss')
+          expect(subject.tree).to eq('https://github.com/gitlab-org/gitlab-foss/tree/hash')
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'handles urls with no .git on the end' do
         stub_url('http://github.com/gitlab-org/gitlab-foss')
-        expect(subject).to eq(['https://github.com/gitlab-org/gitlab-foss', 'https://github.com/gitlab-org/gitlab-foss/tree/hash'])
+        aggregate_failures do
+          expect(subject.web).to eq('https://github.com/gitlab-org/gitlab-foss')
+          expect(subject.tree).to eq('https://github.com/gitlab-org/gitlab-foss/tree/hash')
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'returns original with non-standard url' do
         stub_url('http://github.com/another/gitlab-org/gitlab-foss.git')
-        expect(subject).to eq([repo.submodule_url_for, nil])
+        aggregate_failures do
+          expect(subject.web).to eq(repo.submodule_url_for)
+          expect(subject.tree).to be_nil
+          expect(subject.compare).to be_nil
+        end
       end
     end
 
@@ -143,39 +215,67 @@
         allow(repo).to receive(:project).and_return(project)
 
         stub_url('./')
-        expect(subject).to eq(["/master-project/#{project.path}", "/master-project/#{project.path}/-/tree/hash"])
+        aggregate_failures do
+          expect(subject.web).to eq("/master-project/#{project.path}")
+          expect(subject.tree).to eq("/master-project/#{project.path}/-/tree/hash")
+          expect(subject.compare).to be_nil
+        end
       end
     end
 
     context 'submodule on gitlab.com' do
       it 'detects ssh' do
         stub_url('git@gitlab.com:gitlab-org/gitlab-foss.git')
-        expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-foss', 'https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash'])
+        aggregate_failures do
+          expect(subject.web).to eq('https://gitlab.com/gitlab-org/gitlab-foss')
+          expect(subject.tree).to eq('https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash')
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'detects http' do
         stub_url('http://gitlab.com/gitlab-org/gitlab-foss.git')
-        expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-foss', 'https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash'])
+        aggregate_failures do
+          expect(subject.web).to eq('https://gitlab.com/gitlab-org/gitlab-foss')
+          expect(subject.tree).to eq('https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash')
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'detects https' do
         stub_url('https://gitlab.com/gitlab-org/gitlab-foss.git')
-        expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-foss', 'https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash'])
+        aggregate_failures do
+          expect(subject.web).to eq('https://gitlab.com/gitlab-org/gitlab-foss')
+          expect(subject.tree).to eq('https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash')
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'handles urls with no .git on the end' do
         stub_url('http://gitlab.com/gitlab-org/gitlab-foss')
-        expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-foss', 'https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash'])
+        aggregate_failures do
+          expect(subject.web).to eq('https://gitlab.com/gitlab-org/gitlab-foss')
+          expect(subject.tree).to eq('https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash')
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'handles urls with trailing whitespace' do
         stub_url('http://gitlab.com/gitlab-org/gitlab-foss.git  ')
-        expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-foss', 'https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash'])
+        aggregate_failures do
+          expect(subject.web).to eq('https://gitlab.com/gitlab-org/gitlab-foss')
+          expect(subject.tree).to eq('https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash')
+          expect(subject.compare).to be_nil
+        end
       end
 
       it 'returns original with non-standard url' do
         stub_url('http://gitlab.com/another/gitlab-org/gitlab-foss.git')
-        expect(subject).to eq([repo.submodule_url_for, nil])
+        aggregate_failures do
+          expect(subject.web).to eq(repo.submodule_url_for)
+          expect(subject.tree).to be_nil
+          expect(subject.compare).to be_nil
+        end
       end
     end
 
@@ -183,25 +283,29 @@
       it 'sanitizes unsupported protocols' do
         stub_url('javascript:alert("XSS");')
 
-        expect(subject).to eq([nil, nil])
+        expect(subject).to be_nil
       end
 
       it 'sanitizes unsupported protocols disguised as a repository URL' do
         stub_url('javascript:alert("XSS");foo/bar.git')
 
-        expect(subject).to eq([nil, nil])
+        expect(subject).to be_nil
       end
 
       it 'sanitizes invalid URL with extended ASCII' do
         stub_url('é')
 
-        expect(subject).to eq([nil, nil])
+        expect(subject).to be_nil
       end
 
       it 'returns original' do
         stub_url('http://mygitserver.com/gitlab-org/gitlab-foss')
 
-        expect(subject).to eq([repo.submodule_url_for, nil])
+        aggregate_failures do
+          expect(subject.web).to eq(repo.submodule_url_for)
+          expect(subject.tree).to be_nil
+          expect(subject.compare).to be_nil
+        end
       end
     end
 
@@ -214,7 +318,11 @@ def expect_relative_link_to_resolve_to(relative_path, expected_path)
         stub_url(relative_path)
         result = subject
 
-        expect(result).to eq([expected_path, "#{expected_path}/-/tree/#{submodule_item.id}"])
+        aggregate_failures do
+          expect(result.web).to eq(expected_path)
+          expect(result.tree).to eq("#{expected_path}/-/tree/#{submodule_item.id}")
+          expect(result.compare).to be_nil
+        end
       end
 
       it 'handles project under same group' do
@@ -235,7 +343,7 @@ def expect_relative_link_to_resolve_to(relative_path, expected_path)
 
           result = subject
 
-          expect(result).to eq([nil, nil])
+          expect(result).to be_nil
         end
       end
 
@@ -245,7 +353,7 @@ def expect_relative_link_to_resolve_to(relative_path, expected_path)
 
           result = subject
 
-          expect(result).to eq([nil, nil])
+          expect(result).to be_nil
         end
       end
 
@@ -289,7 +397,7 @@ def expect_relative_link_to_resolve_to(relative_path, expected_path)
       end
 
       it 'returns no links' do
-        expect(subject).to eq([nil, nil])
+        expect(subject).to be_nil
       end
     end
   end
diff --git a/spec/lib/gitlab/submodule_links_spec.rb b/spec/lib/gitlab/submodule_links_spec.rb
index c69326e12bea6ec1db7b8c91b93d369cabd41975..e2bbda81780024f2b62f57dc4228de0f55aa9509 100644
--- a/spec/lib/gitlab/submodule_links_spec.rb
+++ b/spec/lib/gitlab/submodule_links_spec.rb
@@ -18,7 +18,7 @@
       end
 
       it 'returns no links' do
-        expect(subject).to eq([nil, nil])
+        expect(subject).to be_nil
       end
     end
 
@@ -28,17 +28,28 @@
       end
 
       it 'returns no links' do
-        expect(subject).to eq([nil, nil])
+        expect(subject).to be_nil
       end
     end
 
     context 'when the submodule is known' do
       before do
-        stub_urls({ 'gitlab-foss' => 'git@gitlab.com:gitlab-org/gitlab-foss.git' })
+        gitlab_foss = 'git@gitlab.com:gitlab-org/gitlab-foss.git'
+
+        stub_urls({
+          'ref' => { 'gitlab-foss' => gitlab_foss },
+          'other_ref' => { 'gitlab-foss' => gitlab_foss },
+          'signed-commits' => { 'gitlab-foss' => gitlab_foss },
+          'special_ref' => { 'gitlab-foss' => 'git@OTHER.com:gitlab-org/gitlab-foss.git' }
+        })
       end
 
       it 'returns links and caches the by ref' do
-        expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-foss', 'https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash'])
+        aggregate_failures do
+          expect(subject.web).to eq('https://gitlab.com/gitlab-org/gitlab-foss')
+          expect(subject.tree).to eq('https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash')
+          expect(subject.compare).to be_nil
+        end
 
         cache_store = links.instance_variable_get("@cache_store")
 
@@ -49,13 +60,46 @@
         let(:ref) { 'signed-commits' }
 
         it 'returns links' do
-          expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-foss', 'https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash'])
+          aggregate_failures do
+            expect(subject.web).to eq('https://gitlab.com/gitlab-org/gitlab-foss')
+            expect(subject.tree).to eq('https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash')
+            expect(subject.compare).to be_nil
+          end
+        end
+      end
+
+      context 'and the diff information is available' do
+        let(:old_ref) { 'other_ref' }
+        let(:diff_file) { double(old_blob: double(id: 'old-hash', path: 'gitlab-foss'), old_content_sha: old_ref) }
+
+        subject { links.for(submodule_item, ref, diff_file) }
+
+        it 'the returned links include the compare link' do
+          aggregate_failures do
+            expect(subject.web).to eq('https://gitlab.com/gitlab-org/gitlab-foss')
+            expect(subject.tree).to eq('https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash')
+            expect(subject.compare).to eq('https://gitlab.com/gitlab-org/gitlab-foss/-/compare/old-hash...hash')
+          end
+        end
+
+        context 'but the submodule url has changed' do
+          let(:old_ref) { 'special_ref' }
+
+          it 'the returned links do not include the compare link' do
+            aggregate_failures do
+              expect(subject.web).to eq('https://gitlab.com/gitlab-org/gitlab-foss')
+              expect(subject.tree).to eq('https://gitlab.com/gitlab-org/gitlab-foss/-/tree/hash')
+              expect(subject.compare).to be_nil
+            end
+          end
         end
       end
     end
   end
 
-  def stub_urls(urls)
-    allow(repo).to receive(:submodule_urls_for).and_return(urls)
+  def stub_urls(urls_by_ref)
+    allow(repo).to receive(:submodule_urls_for) do |ref|
+      urls_by_ref[ref] if urls_by_ref
+    end
   end
 end
diff --git a/spec/serializers/diff_file_base_entity_spec.rb b/spec/serializers/diff_file_base_entity_spec.rb
index bf69a50a072b771f8aceb6c94043e24301a7a81c..94c39e117903fc463f3dfb1fe90d04bde6691dcc 100644
--- a/spec/serializers/diff_file_base_entity_spec.rb
+++ b/spec/serializers/diff_file_base_entity_spec.rb
@@ -7,21 +7,50 @@
   let(:repository) { project.repository }
   let(:entity) { described_class.new(diff_file, options).as_json }
 
-  context 'diff for a changed submodule' do
-    let(:commit_sha_with_changed_submodule) do
-      "cfe32cf61b73a0d5e9f13e774abde7ff789b1660"
-    end
-
-    let(:commit) { project.commit(commit_sha_with_changed_submodule) }
+  context 'submodule information for a' do
+    let(:commit_sha) { "" }
+    let(:commit) { project.commit(commit_sha) }
     let(:options) { { request: {}, submodule_links: Gitlab::SubmoduleLinks.new(repository) } }
     let(:diff_file) { commit.diffs.diff_files.to_a.last }
 
-    it do
-      expect(entity[:submodule]).to eq(true)
-      expect(entity[:submodule_link]).to eq("https://github.com/randx/six")
-      expect(entity[:submodule_tree_url]).to eq(
-        "https://github.com/randx/six/tree/409f37c4f05865e4fb208c771485f211a22c4c2d"
-      )
+    context 'newly added submodule' do
+      let(:commit_sha) { "cfe32cf61b73a0d5e9f13e774abde7ff789b1660" }
+
+      it 'says it is a submodule and contains links' do
+        expect(entity[:submodule]).to eq(true)
+        expect(entity[:submodule_link]).to eq("https://github.com/randx/six")
+        expect(entity[:submodule_tree_url]).to eq(
+          "https://github.com/randx/six/tree/409f37c4f05865e4fb208c771485f211a22c4c2d"
+        )
+      end
+
+      it 'has no compare url because the submodule was newly added' do
+        expect(entity[:submodule_compare]).to eq(nil)
+      end
+    end
+
+    context 'changed submodule' do
+      # Test repo does not contain a commit that changes a submodule, so we have create such a commit here
+      let(:commit_sha) { repository.update_submodule(project.members[0].user, "six", "c6bc3aa2ee76cadaf0cbd325067c55450997632e", message: "Go one commit back", branch: "master") }
+
+      it 'contains a link to compare the changes' do
+        expect(entity[:submodule_compare]).to eq(
+          {
+            url: "https://github.com/randx/six/compare/409f37c4f05865e4fb208c771485f211a22c4c2d...c6bc3aa2ee76cadaf0cbd325067c55450997632e",
+            old_sha: "409f37c4f05865e4fb208c771485f211a22c4c2d",
+            new_sha: "c6bc3aa2ee76cadaf0cbd325067c55450997632e"
+          }
+        )
+      end
+    end
+
+    context 'normal file (no submodule)' do
+      let(:commit_sha) { '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' }
+
+      it 'sets submodule to false' do
+        expect(entity[:submodule]).to eq(false)
+        expect(entity[:submodule_compare]).to eq(nil)
+      end
     end
   end