From 26b6c243fc8039483537810f6e9cc20a3c56055c Mon Sep 17 00:00:00 2001
From: Doug Hellmann <doug@doughellmann.com>
Date: Wed, 18 Jan 2017 16:22:02 -0500
Subject: [PATCH] use sha1 hashes to sort constraints

We want to avoid packages with common names sorting close together in
the constraints list, because when we batch-release from projects like
Oslo we have a lot of merge conflicts trying to land those individual
patches. This changes the constraints list to be sorted based on the
sha1 hash of the dependency name (ignoring version information so we
have some consistency in the ordering). This gives us the appearance of
randomness while maintaining a stable sort.

Change-Id: I761967f8281c4d5df02e1dc88895b76d9ac89e6e
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
---
 openstack_requirements/cmds/generate.py | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/openstack_requirements/cmds/generate.py b/openstack_requirements/cmds/generate.py
index 52af12aaa9..c55a4184a9 100644
--- a/openstack_requirements/cmds/generate.py
+++ b/openstack_requirements/cmds/generate.py
@@ -14,10 +14,12 @@
 from __future__ import print_function
 
 import copy
+import hashlib
 import optparse
 import os.path
 import subprocess
 import sys
+import textwrap
 
 import fixtures
 
@@ -188,6 +190,19 @@ def _parse_blacklist(path):
         return [l.strip() for l in f]
 
 
+def _make_sort_key(line):
+    """Produce a key that is unlikely to place similar values together.
+
+    We want to avoid sorting all of the oslo libraries together (or
+    all of the python-*client libraries) so when we do batch releases
+    we do not have merge conflicts in the individual patches updating
+    the constraints.
+
+    """
+    dep = line.partition('=')[0].encode('utf-8')
+    return hashlib.sha1(dep).digest()
+
+
 def main(argv=None, stdout=None):
     parser = optparse.OptionParser()
     parser.add_option(
@@ -214,5 +229,12 @@ def main(argv=None, stdout=None):
         _freeze(options.requirements, python) for python in options.pythons]
     _clone_versions(freezes, options)
     blacklist = _parse_blacklist(options.blacklist)
-    stdout.writelines(_combine_freezes(freezes, blacklist))
+    stdout.write(textwrap.dedent('''\
+    # This file is automatically generated using a sort order
+    # intended to reduce collisions when individual lines are
+    # updated in separate patches.
+    #
+    '''))
+    frozen = sorted(_combine_freezes(freezes, blacklist), key=_make_sort_key)
+    stdout.writelines(frozen)
     stdout.flush()