diff --git a/obs-mismatching-source-requests/Dockerfile b/obs-mismatching-source-requests/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..d9e6068ce5ba5f89c656532f33f12f510caa126b
--- /dev/null
+++ b/obs-mismatching-source-requests/Dockerfile
@@ -0,0 +1,8 @@
+FROM debian:buster-slim
+
+RUN apt-get update && apt-get install -y \
+    ca-certificates \
+    osc \
+    python-jinja2 \
+    --no-install-recommends \
+    && rm -rf /var/lib/apt/lists/*
diff --git a/obs-mismatching-source-requests/Jenkinsfile b/obs-mismatching-source-requests/Jenkinsfile
new file mode 100644
index 0000000000000000000000000000000000000000..68380539a6240bbb20e8e041f1e6a5c5e8a22c81
--- /dev/null
+++ b/obs-mismatching-source-requests/Jenkinsfile
@@ -0,0 +1,26 @@
+pipeline {
+  agent {
+    dockerfile {
+      filename 'obs-mismatching-source-requests/Dockerfile'
+    }
+  }
+  environment {
+    RELEASE = "v2020dev0"
+    OSCRC = credentials("349d474d-a6d0-4ab0-987a-a3843916bdff")
+  }
+  stages {
+    stage('Test') {
+      steps {
+        sh '''
+          export HOME=/tmp # switch to a writable folder so OSC can create the `~/.osc_cookiejar` file
+          ./obs-mismatching-source-requests/obs-mismatching-source-requests --oscrc "$OSCRC" --release $RELEASE --junit junit.xml
+        '''
+      }
+    }
+  }
+  post {
+    success {
+      junit 'junit.xml'
+    }
+  }
+}
diff --git a/obs-mismatching-source-requests/obs-mismatching-source-requests b/obs-mismatching-source-requests/obs-mismatching-source-requests
new file mode 100755
index 0000000000000000000000000000000000000000..cd146f74ff3a31d233bef24e28a3f5c97662d2c0
--- /dev/null
+++ b/obs-mismatching-source-requests/obs-mismatching-source-requests
@@ -0,0 +1,125 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+
+import argparse
+import sys
+import textwrap
+
+import jinja2
+import osc.conf
+import osc.core
+
+try:
+  from xml.etree import cElementTree as ET
+except ImportError:
+  import cElementTree as ET
+
+class File:
+  def __init__(self, name, md5):
+    self.name = name
+    self.md5 = md5
+
+class Mismatch:
+  def __init__(self, url, src_project, dsc, rev, tgt_project, src_md5, tgt_md5):
+    self.url = url
+    self.src_project = src_project
+    self.dsc = dsc
+    self.rev = rev
+    self.tgt_project = tgt_project
+    self.tgt_md5 = tgt_md5
+    self.src_md5 = src_md5
+
+def get_xpath(state, projectprefix):
+  xpath = 'state/@name="{}"'.format(state)
+  if projectprefix:
+    xpath += ' and (starts-with(source/@project, "{0}") or starts-with(target/@project, "{0}"))'.format(projectprefix)
+  return xpath
+
+def get_requests(apiurl, xpath):
+  res = osc.core.search(apiurl, request=xpath)
+  collection = res['request']
+  requests = []
+  for root in collection.findall('request'):
+    r = osc.core.Request()
+    r.read(root)
+    requests.append(r)
+  return requests
+
+def get_files(apiurl, project, package, revision=None):
+  xml = osc.core.show_files_meta(apiurl, project, package, revision=revision)
+  filelist = ET.fromstring(xml)
+  ret = []
+  for entry in filelist.findall('entry'):
+    if entry.get('name').startswith('_service:'):
+      continue
+    f = File(entry.get('name'), entry.get('md5'))
+    ret.append(f)
+  return ret
+
+def requesturl(apiurl, reqid):
+  return apiurl+"/request/show/"+reqid
+
+def get_mismatches(oscrc=None, projectprefix=None):
+  osc.conf.get_config(override_conffile = oscrc)
+  apiurl = osc.conf.config['apiurl']
+  xpath = get_xpath('review', projectprefix)
+  requests = get_requests(apiurl, xpath)
+  mismatches = []
+  for r in requests:
+    requrl = requesturl(apiurl, r.reqid)
+    try:
+      submit = next(iter(r.get_actions('submit')), None)
+      if not submit:
+        continue
+      src_filelist = get_files(apiurl, submit.src_project, submit.src_package, revision=submit.src_rev)
+      tgt_filelist = get_files(apiurl, submit.tgt_project, submit.tgt_package)
+      src_dsc = next((f for f in src_filelist if f.name.endswith('.dsc')), None)
+      tgt_dsc = next((f for f in tgt_filelist if f.name.endswith('.dsc')), None)
+      if tgt_dsc and src_dsc.name == tgt_dsc.name and src_dsc.md5 != tgt_dsc.md5:
+        mismatches.append(Mismatch(requrl, submit.src_project, src_dsc.name, submit.src_rev, submit.tgt_project, src_dsc.md5, tgt_dsc.md5))
+    except:
+      print('error while fetching data for request {}'.format(requrl), file=sys.stderr)
+      raise
+  return mismatches
+
+def render_junit(project, release, mismatches):
+  junit_template = textwrap.dedent("""\
+    <?xml version="1.0" encoding="UTF-8"?>
+    <testsuites tests="{{mismatches|length}}" failures="{{mismatches|length}}">
+      <testsuite name="{{project}}:{{release}}">
+        {%- for m in mismatches %}
+        <testcase name="{{m.dsc|replace(".dsc", "")}}">
+          <failure type="failed">
+            {{m.url}} {{m.src_project}} {{m.dsc}} rev{{m.rev}} ({{m.src_md5}}) -> {{m.tgt_project}} ({{m.tgt_md5}})
+          </failure>
+        </testcase>
+        {%- endfor %}
+      </testsuite>
+    </testsuites>
+  """)
+  environment = jinja2.Environment(autoescape=True)
+  template = environment.from_string(junit_template)
+  result = template.render(
+    project=project,
+    release=release,
+    mismatches=mismatches,
+  )
+  return result
+
+def main():
+  parser = argparse.ArgumentParser(description='List OBS request with conflicting .dsc files')
+  parser.add_argument('--release', type=str, metavar='RELEASE',
+                      help='restrict the search to requests sourceing or targeting projects in this release')
+  parser.add_argument('--oscrc', type=str, help='the OSC configuration with the OBS credentials')
+  parser.add_argument('--junit', type=argparse.FileType('w'), help='file to store results in JUnit format')
+  args = parser.parse_args()
+  projectprefix = 'apertis:'+args.release if args.release else None
+  mismatches = get_mismatches(args.oscrc, projectprefix)
+  for m in mismatches:
+    print("{m.url} {m.src_project} {m.dsc} rev{m.rev} ({m.src_md5}) -> {m.tgt_project} ({m.tgt_md5})".format(m=m))
+  if args.junit:
+    args.junit.write(render_junit('apertis', args.release, mismatches))
+
+if __name__ == '__main__':
+  main()