# ============================================================================
# Copyright (c) 2011-2012 University of Pennsylvania
# Copyright (c) 2013-2014 Andreas Schuh
# All rights reserved.
#
# See COPYING file for license information or visit
# http://opensource.andreasschuh.com/cmake-basis/download.html#license
# ============================================================================

##############################################################################
# @file  RevisionTools.cmake
# @brief CMake functions and macros related to revision control systems.
#
# @ingroup CMakeTools
##############################################################################

if (__BASIS_REVISIONTOOLS_INCLUDED)
  return ()
else ()
  set (__BASIS_REVISIONTOOLS_INCLUDED TRUE)
endif ()


# ============================================================================
# required commands
# ============================================================================

find_package (Subversion QUIET)
find_package (Git        QUIET)


## @addtogroup CMakeUtilities
#  @{


# ============================================================================
# Subversion
# ============================================================================

# ----------------------------------------------------------------------------
## @brief Get current revision of file or directory.
#
# @param [in]  URL  Absolute path to directory or file. May also be a URL to the
#                   directory or file in the repository. A leading "file://" is
#                   automatically removed such that the svn command treats it as a
#                   local path.
# @param [out] REV  The revision number of URL. If URL is not under revision
#                   control or Subversion_SVN_EXECUTABLE is invalid, "0" is returned.
#
# @returns Sets @p REV to the revision of the working copy/repository
#          at URL @p URL.
function (basis_svn_get_revision URL REV)
  set (OUT 0)
  if (Subversion_SVN_EXECUTABLE)
    # remove "file://" from URL
    string (REGEX REPLACE "file://" "" TMP "${URL}")
    # retrieve SVN info
    execute_process (
      COMMAND         "${Subversion_SVN_EXECUTABLE}" info "${TMP}"
      OUTPUT_VARIABLE OUT
      ERROR_QUIET
      OUTPUT_STRIP_TRAILING_WHITESPACE
    )
    if (BASIS_DEBUG)
      message ("** basis_svn_get_revision()")
      message ("**   svn info: ${OUT}")
    endif ()
    # extract revision
    if (OUT MATCHES "^(.*\n)?Revision: ([^\n]+).*" AND NOT CMAKE_MATCH_2 STREQUAL "")
      set (OUT "${CMAKE_MATCH_2}")
    else ()
      set (OUT 0)
    endif ()
  endif ()
  # return
  set ("${REV}" "${OUT}" PARENT_SCOPE)
endfunction ()

# ----------------------------------------------------------------------------
## @brief Get revision number when directory or file was last changed.
#
# @param [in]  URL  Absolute path to directory or file. May also be a URL to the
#                   directory or file in the repository. A leading "file://" is
#                   automatically removed such that the svn command treats it as a
#                   local path.
# @param [out] REV  Revision number when URL was last modified. If URL is not
#                   under Subversion control or Subversion_SVN_EXECUTABLE is invalid,
#                   "0" is returned.
#
# @returns Sets @p REV to revision number at which the working copy/repository
#          specified by the URL @p URL was last modified.
function (basis_svn_get_last_changed_revision URL REV)
  set (OUT 0)
  if (Subversion_SVN_EXECUTABLE)
    # remove "file://" from URL
    string (REGEX REPLACE "file://" "" TMP "${URL}")
    # retrieve SVN info
    execute_process (
      COMMAND         "${Subversion_SVN_EXECUTABLE}" info "${TMP}"
      OUTPUT_VARIABLE OUT
      ERROR_QUIET
      OUTPUT_STRIP_TRAILING_WHITESPACE
    )
    if (BASIS_DEBUG)
      message ("** basis_svn_get_revision()")
      message ("**   svn info: ${OUT}")
    endif ()
    # extract last changed revision
    if (OUT MATCHES "^(.*\n)?Last Changed Rev: ([^\n]+).*" AND NOT CMAKE_MATCH_2 STREQUAL "")
      set (OUT "${CMAKE_MATCH_2}")
    else ()
      set (OUT 0)
    endif ()
  endif ()
  # return
  set ("${REV}" "${OUT}" PARENT_SCOPE)
endfunction ()

# ----------------------------------------------------------------------------
## @brief Get status of revision controlled file.
#
# @param [in]  URL    Absolute path to directory or file. May also be a URL to
#                     the directory or file in the repository.
#                     A leading "file://" will be removed such that the svn
#                     command treats it as a local path.
# @param [out] STATUS The status of URL as returned by 'svn status'.
#                     If the local directory or file is unmodified, an
#                     empty string is returned. An empty string is also
#                     returned when Subversion_SVN_EXECUTABLE is invalid.
#
# @returns Sets @p STATUS to the output of the <tt>svn info</tt> command.
function (basis_svn_status URL STATUS)
  if (Subversion_SVN_EXECUTABLE)
    # remove "file://" from URL
    string (REGEX REPLACE "file://" "" TMP "${URL}")

    # retrieve SVN status of URL
    execute_process (
      COMMAND         "${Subversion_SVN_EXECUTABLE}" status "${TMP}"
      OUTPUT_VARIABLE OUT
      ERROR_QUIET
    )

    # return
    set ("${STATUS}" "${OUT}" PARENT_SCOPE)
  else ()
    set ("${STATUS}" "" PARENT_SCOPE)
  endif ()
endfunction ()

# ============================================================================
# Git
# ============================================================================

# ----------------------------------------------------------------------------
# @brief Determine whether or not a given directory is a Git repository
function (basis_is_git_repository FLAG DIR)
  if (GITCOMMAND AND NOT GIT_EXECUTABLE)
    set (GIT_EXECUTABLE GITCOMMAND)
  endif ()
  if (GIT_EXECUTABLE)
    execute_process (
      COMMAND "${GIT_EXECUTABLE}" rev-parse
      WORKING_DIRECTORY "${DIR}"
      RESULT_VARIABLE RETVAL
      OUTPUT_QUIET
      ERROR_QUIET
    )
  else ()
    set (RETVAL 1)
  endif ()
  if (RETVAL EQUAL 0)
    set (${FLAG} "TRUE" PARENT_SCOPE)
  else ()
    set (${FLAG} "FALSE" PARENT_SCOPE)
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Get HEAD commit SHA of file or directory.
#
# @param [in]  URL  Absolute path to repository directory or single file.
# @param [out] REV  The short commit SHA when URL was last modified. If URL
#                   is not under Git control or GIT_EXECUTABLE is invalid,
#                   "0" is returned.
# @param [in]  ARGN Length of commit SHA to return.
#
# @returns Sets @p REV either to the HEAD commit SHA of the repository at
#          directory @p URL or the last commit which modified the file.
function (basis_git_get_revision URL REV)
  if (ARGC GREATER 3)
    message (FATAL_ERROR "basis_git_get_revision: Too many arguments")
  endif ()
  set (OUT 0)
  if (GITCOMMAND AND NOT GIT_EXECUTABLE)
    set (GIT_EXECUTABLE GITCOMMAND)
  endif ()
  if (GIT_EXECUTABLE)
    # remove "file://" from URL
    string (REGEX REPLACE "file://" "" DIR "${URL}")
    # retrieve Git commit SHA of HEAD
    if (IS_DIRECTORY "${DIR}")
      execute_process (
        COMMAND "${GIT_EXECUTABLE}" rev-parse HEAD
        WORKING_DIRECTORY "${DIR}"
        OUTPUT_VARIABLE OUT
        ERROR_QUIET
        OUTPUT_STRIP_TRAILING_WHITESPACE
      )
      if (BASIS_DEBUG)
        message ("** basis_git_get_revision()")
        message ("**   DIR: ${DIR}")
        message ("**   OUT: ${OUT}")
      endif ()
    # retrieve Git commit SHA when file was last modified
    else ()
      basis_get_filename_component(OBJ "${DIR}" NAME)
      basis_get_filename_component(DIR "${DIR}" PATH)
      execute_process (
        COMMAND "${GIT_EXECUTABLE}" log -n 1 -- "${OBJ}"
        WORKING_DIRECTORY "${DIR}"
        OUTPUT_VARIABLE OUT
        ERROR_QUIET
        OUTPUT_STRIP_TRAILING_WHITESPACE
      )
      if (BASIS_DEBUG)
        message ("** basis_git_get_revision()")
        message ("**   DIR: ${DIR}")
        message ("**   OBJ: ${OBJ}")
        message ("**   OUT: ${OUT}")
      endif ()
      # extract commit SHA
      if (OUT MATCHES "commit ([0-9a-f]+).*" AND NOT CMAKE_MATCH_1 STREQUAL "")
        set (OUT "${CMAKE_MATCH_1}")
      else ()
        set (OUT 0)
      endif ()
    endif ()
  endif ()
  if (ARGC EQUAL 3)
    string (SUBSTRING "${OUT}" 0 "${ARGV2}" OUT)
  endif ()
  # return
  set ("${REV}" "${OUT}" PARENT_SCOPE)
endfunction ()


# ============================================================================
# Meta
# ============================================================================

# ----------------------------------------------------------------------------
## @brief Get revision of file or directory.
#
# @param [in]  URL  Absolute path to directory or single file.
# @param [out] REV  Revision number when directory / repository or file was
#                   last modified, "0" is returned when no known revision
#                   control system is used or revision command not found.
#
# @returns Revision when directory of file was last modified.
function (basis_get_revision URL REV)
  # remove "file://" from URL
  string (REGEX REPLACE "file://" "" DIR "${URL}")
  # get directory path
  if (NOT IS_DIRECTORY "${URL}")
    basis_get_filename_component (DIR "${URL}" PATH)
  else ()
    set (DIR "${URL}")
  endif ()
  # check if directory is part of a Git repository
  basis_is_git_repository (IS_GIT_REPOSITORY "${DIR}")
  if (IS_GIT_REPOSITORY)
    basis_git_get_revision ("${URL}" OUT 7)
  else ()
    basis_svn_get_last_changed_revision ("${URL}" OUT)
  endif ()
  # return revision
  set ("${REV}" "${OUT}" PARENT_SCOPE)
endfunction ()


## @}
# end of Doxygen group
