/src/luarocks/fetch/git.lua

http://github.com/keplerproject/luarocks · Lua · 165 lines · 108 code · 22 blank · 35 comment · 29 complexity · 780a5827e2f04920869512a6951e7f52 MD5 · raw file

  1. --- Fetch back-end for retrieving sources from GIT.
  2. local git = {}
  3. local unpack = unpack or table.unpack
  4. local fs = require("luarocks.fs")
  5. local dir = require("luarocks.dir")
  6. local vers = require("luarocks.core.vers")
  7. local util = require("luarocks.util")
  8. local cached_git_version
  9. --- Get git version.
  10. -- @param git_cmd string: name of git command.
  11. -- @return table: git version as returned by luarocks.core.vers.parse_version.
  12. local function git_version(git_cmd)
  13. if not cached_git_version then
  14. local version_line = io.popen(fs.Q(git_cmd)..' --version'):read()
  15. local version_string = version_line:match('%d-%.%d+%.?%d*')
  16. cached_git_version = vers.parse_version(version_string)
  17. end
  18. return cached_git_version
  19. end
  20. --- Check if git satisfies version requirement.
  21. -- @param git_cmd string: name of git command.
  22. -- @param version string: required version.
  23. -- @return boolean: true if git matches version or is newer, false otherwise.
  24. local function git_is_at_least(git_cmd, version)
  25. return git_version(git_cmd) >= vers.parse_version(version)
  26. end
  27. --- Git >= 1.7.10 can clone a branch **or tag**, < 1.7.10 by branch only. We
  28. -- need to know this in order to build the appropriate command; if we can't
  29. -- clone by tag then we'll have to issue a subsequent command to check out the
  30. -- given tag.
  31. -- @param git_cmd string: name of git command.
  32. -- @return boolean: Whether Git can clone by tag.
  33. local function git_can_clone_by_tag(git_cmd)
  34. return git_is_at_least(git_cmd, "1.7.10")
  35. end
  36. --- Git >= 1.8.4 can fetch submodules shallowly, saving bandwidth and time for
  37. -- submodules with large history.
  38. -- @param git_cmd string: name of git command.
  39. -- @return boolean: Whether Git can fetch submodules shallowly.
  40. local function git_supports_shallow_submodules(git_cmd)
  41. return git_is_at_least(git_cmd, "1.8.4")
  42. end
  43. --- Git >= 2.10 can fetch submodules shallowly according to .gitmodules configuration, allowing more granularity.
  44. -- @param git_cmd string: name of git command.
  45. -- @return boolean: Whether Git can fetch submodules shallowly according to .gitmodules.
  46. local function git_supports_shallow_recommendations(git_cmd)
  47. return git_is_at_least(git_cmd, "2.10.0")
  48. end
  49. local function git_identifier(git_cmd, ver)
  50. if not (ver:match("^dev%-%d+$") or ver:match("^scm%-%d+$")) then
  51. return nil
  52. end
  53. local pd = io.popen(fs.command_at(fs.current_dir(), fs.Q(git_cmd).." log --pretty=format:%ai_%h -n 1"))
  54. if not pd then
  55. return nil
  56. end
  57. local date_hash = pd:read("*l")
  58. pd:close()
  59. if not date_hash then
  60. return nil
  61. end
  62. local date, time, tz, hash = date_hash:match("([^%s]+) ([^%s]+) ([^%s]+)_([^%s]+)")
  63. date = date:gsub("%-", "")
  64. time = time:gsub(":", "")
  65. return date .. "." .. time .. "." .. hash
  66. end
  67. --- Download sources for building a rock, using git.
  68. -- @param rockspec table: The rockspec table
  69. -- @param extract boolean: Unused in this module (required for API purposes.)
  70. -- @param dest_dir string or nil: If set, will extract to the given directory.
  71. -- @return (string, string) or (nil, string): The absolute pathname of
  72. -- the fetched source tarball and the temporary directory created to
  73. -- store it; or nil and an error message.
  74. function git.get_sources(rockspec, extract, dest_dir, depth)
  75. assert(rockspec:type() == "rockspec")
  76. assert(type(dest_dir) == "string" or not dest_dir)
  77. local git_cmd = rockspec.variables.GIT
  78. local name_version = rockspec.name .. "-" .. rockspec.version
  79. local module = dir.base_name(rockspec.source.url)
  80. -- Strip off .git from base name if present
  81. module = module:gsub("%.git$", "")
  82. local ok, err_msg = fs.is_tool_available(git_cmd, "Git")
  83. if not ok then
  84. return nil, err_msg
  85. end
  86. local store_dir
  87. if not dest_dir then
  88. store_dir = fs.make_temp_dir(name_version)
  89. if not store_dir then
  90. return nil, "Failed creating temporary directory."
  91. end
  92. util.schedule_function(fs.delete, store_dir)
  93. else
  94. store_dir = dest_dir
  95. end
  96. store_dir = fs.absolute_name(store_dir)
  97. local ok, err = fs.change_dir(store_dir)
  98. if not ok then return nil, err end
  99. local command = {fs.Q(git_cmd), "clone", depth or "--depth=1", rockspec.source.url, module}
  100. local tag_or_branch = rockspec.source.tag or rockspec.source.branch
  101. -- If the tag or branch is explicitly set to "master" in the rockspec, then
  102. -- we can avoid passing it to Git since it's the default.
  103. if tag_or_branch == "master" then tag_or_branch = nil end
  104. if tag_or_branch then
  105. if git_can_clone_by_tag(git_cmd) then
  106. -- The argument to `--branch` can actually be a branch or a tag as of
  107. -- Git 1.7.10.
  108. table.insert(command, 3, "--branch=" .. tag_or_branch)
  109. end
  110. end
  111. if not fs.execute(unpack(command)) then
  112. return nil, "Failed cloning git repository."
  113. end
  114. ok, err = fs.change_dir(module)
  115. if not ok then return nil, err end
  116. if tag_or_branch and not git_can_clone_by_tag() then
  117. if not fs.execute(fs.Q(git_cmd), "checkout", tag_or_branch) then
  118. return nil, 'Failed to check out the "' .. tag_or_branch ..'" tag or branch.'
  119. end
  120. end
  121. -- Fetching git submodules is supported only when rockspec format is >= 3.0.
  122. if rockspec:format_is_at_least("3.0") then
  123. command = {fs.Q(git_cmd), "submodule", "update", "--init", "--recursive"}
  124. if git_supports_shallow_recommendations(git_cmd) then
  125. table.insert(command, 5, "--recommend-shallow")
  126. elseif git_supports_shallow_submodules(git_cmd) then
  127. -- Fetch only the last commit of each submodule.
  128. table.insert(command, 5, "--depth=1")
  129. end
  130. if not fs.execute(unpack(command)) then
  131. return nil, 'Failed to fetch submodules.'
  132. end
  133. end
  134. if not rockspec.source.tag then
  135. rockspec.source.identifier = git_identifier(git_cmd, rockspec.version)
  136. end
  137. fs.delete(dir.path(store_dir, module, ".git"))
  138. fs.delete(dir.path(store_dir, module, ".gitignore"))
  139. fs.pop_dir()
  140. fs.pop_dir()
  141. return module, store_dir
  142. end
  143. return git