gitupdate.cmake.in 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. # Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. # file Copyright.txt or https://cmake.org/licensing for details.
  3. cmake_minimum_required(VERSION 3.5)
  4. function(get_hash_for_ref ref out_var err_var)
  5. execute_process(
  6. COMMAND "@git_EXECUTABLE@" rev-parse "${ref}^0"
  7. WORKING_DIRECTORY "@work_dir@"
  8. RESULT_VARIABLE error_code
  9. OUTPUT_VARIABLE ref_hash
  10. ERROR_VARIABLE error_msg
  11. OUTPUT_STRIP_TRAILING_WHITESPACE
  12. )
  13. if(error_code)
  14. set(${out_var} "" PARENT_SCOPE)
  15. else()
  16. set(${out_var} "${ref_hash}" PARENT_SCOPE)
  17. endif()
  18. set(${err_var} "${error_msg}" PARENT_SCOPE)
  19. endfunction()
  20. get_hash_for_ref(HEAD head_sha error_msg)
  21. if(head_sha STREQUAL "")
  22. message(FATAL_ERROR "Failed to get the hash for HEAD:\n${error_msg}")
  23. endif()
  24. execute_process(
  25. COMMAND "@git_EXECUTABLE@" show-ref "@git_tag@"
  26. WORKING_DIRECTORY "@work_dir@"
  27. OUTPUT_VARIABLE show_ref_output
  28. )
  29. if(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/remotes/")
  30. # Given a full remote/branch-name and we know about it already. Since
  31. # branches can move around, we always have to fetch.
  32. set(fetch_required YES)
  33. set(checkout_name "@git_tag@")
  34. elseif(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/tags/")
  35. # Given a tag name that we already know about. We don't know if the tag we
  36. # have matches the remote though (tags can move), so we should fetch.
  37. set(fetch_required YES)
  38. set(checkout_name "@git_tag@")
  39. # Special case to preserve backward compatibility: if we are already at the
  40. # same commit as the tag we hold locally, don't do a fetch and assume the tag
  41. # hasn't moved on the remote.
  42. # FIXME: We should provide an option to always fetch for this case
  43. get_hash_for_ref("@git_tag@" tag_sha error_msg)
  44. if(tag_sha STREQUAL head_sha)
  45. message(VERBOSE "Already at requested tag: ${tag_sha}")
  46. return()
  47. endif()
  48. elseif(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/heads/")
  49. # Given a branch name without any remote and we already have a branch by that
  50. # name. We might already have that branch checked out or it might be a
  51. # different branch. It isn't safe to use a bare branch name without the
  52. # remote, so do a fetch and replace the ref with one that includes the remote.
  53. set(fetch_required YES)
  54. set(checkout_name "@git_remote_name@/@git_tag@")
  55. else()
  56. get_hash_for_ref("@git_tag@" tag_sha error_msg)
  57. if(tag_sha STREQUAL head_sha)
  58. # Have the right commit checked out already
  59. message(VERBOSE "Already at requested ref: ${tag_sha}")
  60. return()
  61. elseif(tag_sha STREQUAL "")
  62. # We don't know about this ref yet, so we have no choice but to fetch.
  63. # We deliberately swallow any error message at the default log level
  64. # because it can be confusing for users to see a failed git command.
  65. # That failure is being handled here, so it isn't an error.
  66. set(fetch_required YES)
  67. set(checkout_name "@git_tag@")
  68. if(NOT error_msg STREQUAL "")
  69. message(VERBOSE "${error_msg}")
  70. endif()
  71. else()
  72. # We have the commit, so we know we were asked to find a commit hash
  73. # (otherwise it would have been handled further above), but we don't
  74. # have that commit checked out yet
  75. set(fetch_required NO)
  76. set(checkout_name "@git_tag@")
  77. if(NOT error_msg STREQUAL "")
  78. message(WARNING "${error_msg}")
  79. endif()
  80. endif()
  81. endif()
  82. if(fetch_required)
  83. message(VERBOSE "Fetching latest from the remote @git_remote_name@")
  84. execute_process(
  85. COMMAND "@git_EXECUTABLE@" fetch --tags --force "@git_remote_name@"
  86. WORKING_DIRECTORY "@work_dir@"
  87. COMMAND_ERROR_IS_FATAL ANY
  88. )
  89. endif()
  90. set(git_update_strategy "@git_update_strategy@")
  91. if(git_update_strategy STREQUAL "")
  92. # Backward compatibility requires REBASE as the default behavior
  93. set(git_update_strategy REBASE)
  94. endif()
  95. if(git_update_strategy MATCHES "^REBASE(_CHECKOUT)?$")
  96. # Asked to potentially try to rebase first, maybe with fallback to checkout.
  97. # We can't if we aren't already on a branch and we shouldn't if that local
  98. # branch isn't tracking the one we want to checkout.
  99. execute_process(
  100. COMMAND "@git_EXECUTABLE@" symbolic-ref -q HEAD
  101. WORKING_DIRECTORY "@work_dir@"
  102. OUTPUT_VARIABLE current_branch
  103. OUTPUT_STRIP_TRAILING_WHITESPACE
  104. # Don't test for an error. If this isn't a branch, we get a non-zero error
  105. # code but empty output.
  106. )
  107. if(current_branch STREQUAL "")
  108. # Not on a branch, checkout is the only sensible option since any rebase
  109. # would always fail (and backward compatibility requires us to checkout in
  110. # this situation)
  111. set(git_update_strategy CHECKOUT)
  112. else()
  113. execute_process(
  114. COMMAND "@git_EXECUTABLE@" for-each-ref "--format='%(upstream:short)'" "${current_branch}"
  115. WORKING_DIRECTORY "@work_dir@"
  116. OUTPUT_VARIABLE upstream_branch
  117. OUTPUT_STRIP_TRAILING_WHITESPACE
  118. COMMAND_ERROR_IS_FATAL ANY # There is no error if no upstream is set
  119. )
  120. if(NOT upstream_branch STREQUAL checkout_name)
  121. # Not safe to rebase when asked to checkout a different branch to the one
  122. # we are tracking. If we did rebase, we could end up with arbitrary
  123. # commits added to the ref we were asked to checkout if the current local
  124. # branch happens to be able to rebase onto the target branch. There would
  125. # be no error message and the user wouldn't know this was occurring.
  126. set(git_update_strategy CHECKOUT)
  127. endif()
  128. endif()
  129. elseif(NOT git_update_strategy STREQUAL "CHECKOUT")
  130. message(FATAL_ERROR "Unsupported git update strategy: ${git_update_strategy}")
  131. endif()
  132. # Check if stash is needed
  133. execute_process(
  134. COMMAND "@git_EXECUTABLE@" status --porcelain
  135. WORKING_DIRECTORY "@work_dir@"
  136. RESULT_VARIABLE error_code
  137. OUTPUT_VARIABLE repo_status
  138. )
  139. if(error_code)
  140. message(FATAL_ERROR "Failed to get the status")
  141. endif()
  142. string(LENGTH "${repo_status}" need_stash)
  143. # If not in clean state, stash changes in order to be able to perform a
  144. # rebase or checkout without losing those changes permanently
  145. if(need_stash)
  146. execute_process(
  147. COMMAND "@git_EXECUTABLE@" stash save @git_stash_save_options@
  148. WORKING_DIRECTORY "@work_dir@"
  149. COMMAND_ERROR_IS_FATAL ANY
  150. )
  151. endif()
  152. if(git_update_strategy STREQUAL "CHECKOUT")
  153. execute_process(
  154. COMMAND "@git_EXECUTABLE@" checkout "${checkout_name}"
  155. WORKING_DIRECTORY "@work_dir@"
  156. COMMAND_ERROR_IS_FATAL ANY
  157. )
  158. else()
  159. execute_process(
  160. COMMAND "@git_EXECUTABLE@" rebase "${checkout_name}"
  161. WORKING_DIRECTORY "@work_dir@"
  162. RESULT_VARIABLE error_code
  163. OUTPUT_VARIABLE rebase_output
  164. ERROR_VARIABLE rebase_output
  165. )
  166. if(error_code)
  167. # Rebase failed, undo the rebase attempt before continuing
  168. execute_process(
  169. COMMAND "@git_EXECUTABLE@" rebase --abort
  170. WORKING_DIRECTORY "@work_dir@"
  171. )
  172. if(NOT git_update_strategy STREQUAL "REBASE_CHECKOUT")
  173. # Not allowed to do a checkout as a fallback, so cannot proceed
  174. if(need_stash)
  175. execute_process(
  176. COMMAND "@git_EXECUTABLE@" stash pop --index --quiet
  177. WORKING_DIRECTORY "@work_dir@"
  178. )
  179. endif()
  180. message(FATAL_ERROR "\nFailed to rebase in: '@work_dir@'."
  181. "\nOutput from the attempted rebase follows:"
  182. "\n${rebase_output}"
  183. "\n\nYou will have to resolve the conflicts manually")
  184. endif()
  185. # Fall back to checkout. We create an annotated tag so that the user
  186. # can manually inspect the situation and revert if required.
  187. # We can't log the failed rebase output because MSVC sees it and
  188. # intervenes, causing the build to fail even though it completes.
  189. # Write it to a file instead.
  190. string(TIMESTAMP tag_timestamp "%Y%m%dT%H%M%S" UTC)
  191. set(tag_name _cmake_ExternalProject_moved_from_here_${tag_timestamp}Z)
  192. set(error_log_file ${CMAKE_CURRENT_LIST_DIR}/rebase_error_${tag_timestamp}Z.log)
  193. file(WRITE ${error_log_file} "${rebase_output}")
  194. message(WARNING "Rebase failed, output has been saved to ${error_log_file}"
  195. "\nFalling back to checkout, previous commit tagged as ${tag_name}")
  196. execute_process(
  197. COMMAND "@git_EXECUTABLE@" tag -a
  198. -m "ExternalProject attempting to move from here to ${checkout_name}"
  199. ${tag_name}
  200. WORKING_DIRECTORY "@work_dir@"
  201. COMMAND_ERROR_IS_FATAL ANY
  202. )
  203. execute_process(
  204. COMMAND "@git_EXECUTABLE@" checkout "${checkout_name}"
  205. WORKING_DIRECTORY "@work_dir@"
  206. COMMAND_ERROR_IS_FATAL ANY
  207. )
  208. endif()
  209. endif()
  210. if(need_stash)
  211. # Put back the stashed changes
  212. execute_process(
  213. COMMAND "@git_EXECUTABLE@" stash pop --index --quiet
  214. WORKING_DIRECTORY "@work_dir@"
  215. RESULT_VARIABLE error_code
  216. )
  217. if(error_code)
  218. # Stash pop --index failed: Try again dropping the index
  219. execute_process(
  220. COMMAND "@git_EXECUTABLE@" reset --hard --quiet
  221. WORKING_DIRECTORY "@work_dir@"
  222. )
  223. execute_process(
  224. COMMAND "@git_EXECUTABLE@" stash pop --quiet
  225. WORKING_DIRECTORY "@work_dir@"
  226. RESULT_VARIABLE error_code
  227. )
  228. if(error_code)
  229. # Stash pop failed: Restore previous state.
  230. execute_process(
  231. COMMAND "@git_EXECUTABLE@" reset --hard --quiet ${head_sha}
  232. WORKING_DIRECTORY "@work_dir@"
  233. )
  234. execute_process(
  235. COMMAND "@git_EXECUTABLE@" stash pop --index --quiet
  236. WORKING_DIRECTORY "@work_dir@"
  237. )
  238. message(FATAL_ERROR "\nFailed to unstash changes in: '@work_dir@'."
  239. "\nYou will have to resolve the conflicts manually")
  240. endif()
  241. endif()
  242. endif()
  243. set(init_submodules "@init_submodules@")
  244. if(init_submodules)
  245. execute_process(
  246. COMMAND "@git_EXECUTABLE@" submodule update @git_submodules_recurse@ --init @git_submodules@
  247. WORKING_DIRECTORY "@work_dir@"
  248. COMMAND_ERROR_IS_FATAL ANY
  249. )
  250. endif()