quickmerge: Adjustment to autotest quickmerge to support py3

Autotest will be using a new circular symlink in
files/client/autotest_lib which points back to client to support
python3 migration (setup_modules basically requires this symlink,
or a massive re-write). Adjusting quickmerge to handle this
circular link

BUG=chromium:990593
TEST=test_that dummy_Pass, with my circular link

Change-Id: Ibd6e2f15abd1fbda909fd912e6e028745e257c5d
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/2590587
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Prathmesh Prabhu <pprabhu@google.com>
Commit-Queue: Derek Beckett <dbeckett@chromium.org>
Tested-by: Derek Beckett <dbeckett@chromium.org>
diff --git a/scripts/autotest_quickmerge.py b/scripts/autotest_quickmerge.py
index 850f3a4..7bcb266 100644
--- a/scripts/autotest_quickmerge.py
+++ b/scripts/autotest_quickmerge.py
@@ -35,6 +35,7 @@
 
 
 INCLUDE_PATTERNS_FILENAME = 'autotest-quickmerge-includepatterns'
+AUTOTEST_SYMLINK = 'autotest_lib'
 AUTOTEST_PROJECT_NAME = 'chromiumos/third_party/autotest'
 AUTOTEST_EBUILD = 'chromeos-base/autotest'
 DOWNGRADE_EBUILDS = ['chromeos-base/autotest']
@@ -281,17 +282,24 @@
   command += ['--exclude=**.pyc']
   command += ['--exclude=**.pyo']
 
+  # Always exclude the autotest symlink to avoid a possible recursion hole.
+  # The order here is (unfortunately) extremely important.
+  if AUTOTEST_SYMLINK not in source_path:
+    command += ['--exclude=%s/' % AUTOTEST_SYMLINK]
+
   # Exclude files with a specific substring in their name, because
   # they create an ambiguous itemized report. (see unit test file for details)
   command += ['--exclude=** -> *']
 
+  # Order seems important here, and this include must come before the possible
+  # exclude below...
   if include_pattern_file:
     command += ['--include-from=%s' % include_pattern_file]
 
-  command += ['--exclude=*']
-
-  # Some tests use symlinks. Follow these.
-  command += ['-L']
+  if AUTOTEST_SYMLINK in source_path:
+    command += ['-l']
+  else:
+    command += ['-L', '--exclude=*']
 
   command += [source_path, sysroot_autotest_path]
 
@@ -328,6 +336,17 @@
   return parser.parse_args(argv)
 
 
+def _maybe_add_autotest_symlink(src_paths, path, dest_path):
+  """If the symlink folders exists, add them to the src to quickmerge."""
+  autotest_client_symlink = os.path.join(path, 'client', AUTOTEST_SYMLINK)
+  if os.path.exists(autotest_client_symlink):
+    src_paths.append((autotest_client_symlink,
+                      os.path.join(dest_path, 'client/')))
+  autotest_main_symlink = os.path.join(path, AUTOTEST_SYMLINK)
+  if os.path.exists(autotest_main_symlink):
+    src_paths.append((autotest_main_symlink, dest_path))
+
+
 def main(argv):
   cros_build_lib.AssertInsideChroot()
 
@@ -371,10 +390,21 @@
       logging.error('Could not quickmerge for project: %s',
                     os.path.basename(quickmerge_file))
 
+  # Autotest uses a circular symlink in client that *must* be added after the
+  # other sections of Autotest.
+  src_paths = list(src_paths)
+
+  # All destination paths up to this point are the same, but other sources
+  # added below might have a different one.
+  src_dest_paths = [(src_path + '/', sysroot_autotest_path)
+                    for src_path in src_paths]
+
+  _maybe_add_autotest_symlink(src_dest_paths, brillo_autotest_src_path,
+                              sysroot_autotest_path)
   num_new_files = 0
   num_modified_files = 0
-  for src_path in src_paths:
-    rsync_output = RsyncQuickmerge(src_path +'/', sysroot_autotest_path,
+  for src_path, dest_path in src_dest_paths:
+    rsync_output = RsyncQuickmerge(src_path, dest_path,
                                    include_pattern_file, args.pretend,
                                    args.overwrite)