fetch-and-push-remote: Add fallback for missing remote tags

We're currently missing Qualcomm release tags in the newly set up
CodeLinaro repositories. The manifests, release branches and sources are
available though. Create the tags locally if missing on remote. For our
purposes, this will be equivalent to tags fetched from remote.

Change-Id: I8ff7c1d9d3812e5ebb14f28f7fc597d83f07d83e
diff --git a/bin/fetch-and-push-remote b/bin/fetch-and-push-remote
index 72c8668..bd79bfa 100755
--- a/bin/fetch-and-push-remote
+++ b/bin/fetch-and-push-remote
@@ -83,6 +83,12 @@
                                   branch to push to target gerrit.
 -nt, --new-tag NEW_TAG          : Name of a new tag to create and push.
 -rt, --remote-tag REMOTE_TAG    : Name of remote tag to fetch and import.
+--remote-tag-fallback-new-tag NEW_TAG
+                                : Workaround for CodeLinaro: After migration
+                                  from CodeAurora, some Qualcomm public release
+                                  tags are not availale (yet). In that case,
+                                  create and push a new tag locally instead. Use
+                                  together with --new-tag-annotation-message.
 --new-tag-annotation-message MSG: When creating new tags (see options above),
                                   annotate it with '--annotate -m MSG'
 -pc, --push-current-remote-rev  : Push the remote revisions (tags or branchs)
@@ -342,6 +348,31 @@
 }
 
 
+# Fetch a remote tag and push to the target gerrit; fall back to locally created
+# tag if the source remote doesn't have the tag.
+#
+# Globals:
+#   NEW_TAG_ANNOTATION_MESSAGE
+#   PUSH_SKIP_VALIDATION
+#   REPO_PROJECT
+#   REPO_REMOTE
+#   TARGET_GERRIT_NAME
+#   VERBOSE
+# Arguments:
+#   new_tag    Name of the tag to create and push
+# Returns:
+#   None
+_import_remote_tag_with_new_tag_fallback()
+{
+    local tag="$1"
+
+    log_bold "TAG: Fetch, create if needed, and push ${tag}:"
+
+    repo_forall \
+        "fetch_and_push_tag_to_gerrit_fall_back_to_new_tag \"${tag}\""
+}
+
+
 # Push current remote revisions to the target gerrit
 #
 # Globals:
@@ -457,6 +488,14 @@
             REMOTE_TAG="$2"
             shift 2
             ;;
+        --remote-tag-fallback-new-tag)
+            if [ $# -lt 2 ]; then
+                _usage >&2
+                exit 1
+            fi
+            REMOTE_TAG_WITH_NEW_TAG_FALLBACK="$2"
+            shift 2
+            ;;
         --new-tag-annotation-message)
             if [ $# -lt 2 ]; then
                 _usage >&2
@@ -634,3 +673,8 @@
 if [ -n "${REMOTE_TAG}" ] ; then
     _import_remote_tag "${REMOTE_TAG}"
 fi
+
+if [ -n "${REMOTE_TAG_WITH_NEW_TAG_FALLBACK}" ] ; then
+    _import_remote_tag_with_new_tag_fallback \
+        "${REMOTE_TAG_WITH_NEW_TAG_FALLBACK}"
+fi
diff --git a/shell-libs/repo-forall-functions.sh b/shell-libs/repo-forall-functions.sh
index 172b28d..12c8587 100644
--- a/shell-libs/repo-forall-functions.sh
+++ b/shell-libs/repo-forall-functions.sh
@@ -546,6 +546,82 @@
 }
 
 
+# Fetch tag, or create locally if not available on the source gerrit. Then push
+# to the target gerrit.
+#
+# Globals:
+#   NEW_TAG_ANNOTATION_MESSAGE
+#   PUSH_SKIP_VALIDATION
+#   REPO_PROJECT
+#   REPO_REMOTE
+#   TARGET_GERRIT_NAME
+#   VERBOSE
+# Arguments:
+#   Tag    Name of the tag to push.
+# Returns:
+#   None
+fetch_and_push_tag_to_gerrit_fall_back_to_new_tag()
+{
+    local tag="$1"
+    if [ -z "${tag}" ]; then
+        log_e 'Missing tag parameter'
+    fi
+
+    # Can can have the following cases:
+    # 1. Source gerrit has the tag, target gerrit may or may not have it.
+    #   -> Fetch tag and push to target. That way we will see if the target
+    #      gerrit might have a conflicting version of that tag.
+    #   -> If tags on source and target gerrit conflict, we just fail. To be
+    #      checked manually what to do.
+    # 2. Source gerrit does not, but target gerrit has the tag.
+    #   -> Nothing more to do, just assume the tag is valid.
+    # 3. None of the gerrits has the tag.
+    #   -> Apply the workaround: Create the tag locally.
+
+    local tag_ref="refs/tags/${tag}"
+    local source_fetch_result=0
+    # Run in sub-shell to catch usually expected abort on fetch failure.
+    (
+        fetch_remote_tag "${tag}"
+    ) || source_fetch_result=$?
+
+    if [ "${source_fetch_result}" -eq 0 ]; then
+        # Case 1
+        log_i "Fetch succeeded. Skipping work around with locally created tag."
+    fi
+
+    # Case 2 or 3
+    if [ "${source_fetch_result}" -ne 0 ]; then
+        log_i "Fetch failed. Checking for tag on target gerrit."
+        local target_fetch_result=0
+        (
+            # Modifying global REPO_REMOTE within sub-shell is safe; it won't
+            # propagate back to us.
+            # shellcheck disable=SC2030
+            export REPO_REMOTE="${TARGET_GERRIT_NAME}"
+            fetch_remote_tag "${tag}"
+        ) || target_fetch_result=$?
+        if [ "${target_fetch_result}" -eq 0 ]; then
+            # Case 2: Keep the tag on the target gerrit. Nothing more to do.
+            log_i "Tag already present on target gerrit. Done."
+            return 0
+        fi
+
+        # Case 3
+        log_w "[WORKAROUND] Neither source nor target gerrit have tag ${tag}." \
+            "Creating the tag locally."
+        create_new_tag "${tag}"
+    fi
+
+    # Case 1 or 3 wrap up: Sanity check on local ref and push to target.
+    if ! _local_ref_exists "${tag_ref}"; then
+        log_e "[assert] Tag ${tag} does not exist locally when it should now."
+    fi
+    # This might fail if the target gerrit has a conflicting version of the tag.
+    _git_push "${tag_ref}:${tag_ref}"
+}
+
+
 # Push the remote revision that the manifest is currently pointing to.
 #
 # Globals: