From 58f152a92a0fa3ad6d5f0fcf64501b1108097c69 Mon Sep 17 00:00:00 2001 From: Dan Liew <daniel.liew@imperial.ac.uk> Date: Wed, 23 Aug 2017 19:21:32 +0100 Subject: [PATCH] [CMake] Teach CMake to support git worktrees. This fixes the bug reported by @nbraud reported in #1227. Previously the CMake build system assumed that the `.git` file must be a directory. This is not the case when the working directory is a "git worktree". In this case the `.git` file is just a plain file that points to a directory within the true `.git` directory. This commit essentially implements the logic to traverse this extra level of indirection and removes some assumptions that the `.git` file is a directory. --- cmake/git_utils.cmake | 82 ++++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/cmake/git_utils.cmake b/cmake/git_utils.cmake index f98aca205..dbc95d8df 100644 --- a/cmake/git_utils.cmake +++ b/cmake/git_utils.cmake @@ -4,23 +4,55 @@ # of the git directory changes CMake will be forced to re-run. This useful # for fetching the current git hash and including it in the build. # -# `GIT_DIR` is the path to the git directory (i.e. the `.git` directory) +# `GIT_DOT_FILE` is the path to the git directory (i.e. the `.git` directory) or +# `.git` file used by a git worktree. # `SUCCESS_VAR` is the name of the variable to set. It will be set to TRUE # if the dependency was successfully added and FALSE otherwise. -function(add_git_dir_dependency GIT_DIR SUCCESS_VAR) +function(add_git_dir_dependency GIT_DOT_FILE SUCCESS_VAR) if (NOT "${ARGC}" EQUAL 2) message(FATAL_ERROR "Invalid number (${ARGC}) of arguments") endif() - if (NOT IS_ABSOLUTE "${GIT_DIR}") - message(FATAL_ERROR "GIT_DIR (\"${GIT_DIR}\") is not an absolute path") + if (NOT IS_ABSOLUTE "${GIT_DOT_FILE}") + message(FATAL_ERROR "GIT_DOT_FILE (\"${GIT_DOT_FILE}\") is not an absolute path") endif() - if (NOT IS_DIRECTORY "${GIT_DIR}") - message(FATAL_ERROR "GIT_DIR (\"${GIT_DIR}\") is not a directory") + if (NOT EXISTS "${GIT_DOT_FILE}") + message(FATAL_ERROR "GIT_DOT_FILE (\"${GIT_DOT_FILE}\") does not exist") endif() - set(GIT_HEAD_FILE "${GIT_DIR}/HEAD") + if (NOT IS_DIRECTORY "${GIT_DOT_FILE}") + # Might be a git worktree. In this case we need parse out the worktree + # git directory + file(READ "${GIT_DOT_FILE}" GIT_DOT_FILE_DATA LIMIT 512) + string(STRIP "${GIT_DOT_FILE_DATA}" GIT_DOT_FILE_DATA_STRIPPED) + if ("${GIT_DOT_FILE_DATA_STRIPPED}" MATCHES "^gitdir:[ ]*(.+)$") + # Git worktree + message(STATUS "Found git worktree") + set(GIT_WORKTREE_DIR "${CMAKE_MATCH_1}") + set(GIT_HEAD_FILE "${GIT_WORKTREE_DIR}/HEAD") + # Figure out where real git directory lives + set(GIT_COMMON_DIR_FILE "${GIT_WORKTREE_DIR}/commondir") + if (NOT EXISTS "${GIT_COMMON_DIR_FILE}") + message(FATAL_ERROR "Found git worktree dir but could not find \"${GIT_COMMON_DIR_FILE}\"") + endif() + file(READ "${GIT_COMMON_DIR_FILE}" GIT_COMMON_DIR_FILE_DATA LIMIT 512) + string(STRIP "${GIT_COMMON_DIR_FILE_DATA}" GIT_COMMON_DIR_FILE_DATA_STRIPPED) + get_filename_component(GIT_DIR "${GIT_WORKTREE_DIR}/${GIT_COMMON_DIR_FILE_DATA_STRIPPED}" ABSOLUTE) + if (NOT IS_DIRECTORY "${GIT_DIR}") + message(FATAL_ERROR "Failed to compute path to git directory from git worktree") + endif() + else() + message(FATAL_ERROR "GIT_DOT_FILE (\"${GIT_DOT_FILE}\") is not a directory or a pointer to git worktree directory") + endif() + else() + # Just a normal `.git` directory + message(STATUS "Found simple git working directory") + set(GIT_HEAD_FILE "${GIT_DOT_FILE}/HEAD") + set(GIT_DIR "${GIT_DOT_FILE}") + endif() + message(STATUS "Found git directory \"${GIT_DIR}\"") + if (NOT EXISTS "${GIT_HEAD_FILE}") message(AUTHOR_WARNING "Git head file \"${GIT_HEAD_FILE}\" cannot be found") set(${SUCCESS_VAR} FALSE PARENT_SCOPE) @@ -79,24 +111,25 @@ function(add_git_dir_dependency GIT_DIR SUCCESS_VAR) set(${SUCCESS_VAR} TRUE PARENT_SCOPE) endfunction() -# get_git_head_hash(GIT_DIR OUTPUT_VAR) +# get_git_head_hash(GIT_DOT_FILE OUTPUT_VAR) # -# Retrieve the current commit hash for a git working directory where `GIT_DIR` -# is the `.git` directory in the root of the git working directory. +# Retrieve the current commit hash for a git working directory where +# `GIT_DOT_FILE` is the `.git` directory or `.git` pointer file in a git +# worktree in the root of the git working directory. # # `OUTPUT_VAR` should be the name of the variable to put the result in. If this # function fails then either a fatal error will be raised or `OUTPUT_VAR` will # contain a string with the suffix `NOTFOUND` which can be used in CMake `if()` # commands. -function(get_git_head_hash GIT_DIR OUTPUT_VAR) +function(get_git_head_hash GIT_DOT_FILE OUTPUT_VAR) if (NOT "${ARGC}" EQUAL 2) message(FATAL_ERROR "Invalid number of arguments") endif() - if (NOT IS_DIRECTORY "${GIT_DIR}") - message(FATAL_ERROR "\"${GIT_DIR}\" is not a directory") + if (NOT EXISTS "${GIT_DOT_FILE}") + message(FATAL_ERROR "\"${GIT_DOT_FILE}\" does not exist") endif() - if (NOT IS_ABSOLUTE "${GIT_DIR}") - message(FATAL_ERROR \""${GIT_DIR}\" is not an absolute path") + if (NOT IS_ABSOLUTE "${GIT_DOT_FILE}") + message(FATAL_ERROR \""${GIT_DOT_FILE}\" is not an absolute path") endif() find_package(Git) # NOTE: Use `GIT_FOUND` rather than `Git_FOUND` which was only @@ -105,7 +138,7 @@ function(get_git_head_hash GIT_DIR OUTPUT_VAR) set(${OUTPUT_VAR} "GIT-NOTFOUND" PARENT_SCOPE) return() endif() - get_filename_component(GIT_WORKING_DIR "${GIT_DIR}" DIRECTORY) + get_filename_component(GIT_WORKING_DIR "${GIT_DOT_FILE}" DIRECTORY) execute_process( COMMAND "${GIT_EXECUTABLE}" @@ -128,24 +161,25 @@ function(get_git_head_hash GIT_DIR OUTPUT_VAR) set(${OUTPUT_VAR} "${Z3_GIT_HASH}" PARENT_SCOPE) endfunction() -# get_git_head_describe(GIT_DIR OUTPUT_VAR) +# get_git_head_describe(GIT_DOT_FILE OUTPUT_VAR) # # Retrieve the output of `git describe` for a git working directory where -# `GIT_DIR` is the `.git` directory in the root of the git working directory. +# `GIT_DOT_FILE` is the `.git` directory or `.git` pointer file in a git +# worktree in the root of the git working directory. # # `OUTPUT_VAR` should be the name of the variable to put the result in. If this # function fails then either a fatal error will be raised or `OUTPUT_VAR` will # contain a string with the suffix `NOTFOUND` which can be used in CMake `if()` # commands. -function(get_git_head_describe GIT_DIR OUTPUT_VAR) +function(get_git_head_describe GIT_DOT_FILE OUTPUT_VAR) if (NOT "${ARGC}" EQUAL 2) message(FATAL_ERROR "Invalid number of arguments") endif() - if (NOT IS_DIRECTORY "${GIT_DIR}") - message(FATAL_ERROR "\"${GIT_DIR}\" is not a directory") + if (NOT EXISTS "${GIT_DOT_FILE}") + message(FATAL_ERROR "\"${GIT_DOT_FILE}\" does not exist") endif() - if (NOT IS_ABSOLUTE "${GIT_DIR}") - message(FATAL_ERROR \""${GIT_DIR}\" is not an absolute path") + if (NOT IS_ABSOLUTE "${GIT_DOT_FILE}") + message(FATAL_ERROR \""${GIT_DOT_FILE}\" is not an absolute path") endif() find_package(Git) # NOTE: Use `GIT_FOUND` rather than `Git_FOUND` which was only @@ -154,7 +188,7 @@ function(get_git_head_describe GIT_DIR OUTPUT_VAR) set(${OUTPUT_VAR} "GIT-NOTFOUND" PARENT_SCOPE) return() endif() - get_filename_component(GIT_WORKING_DIR "${GIT_DIR}" DIRECTORY) + get_filename_component(GIT_WORKING_DIR "${GIT_DOT_FILE}" DIRECTORY) execute_process( COMMAND "${GIT_EXECUTABLE}"