gitupdate.cmake.in 11 KB

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