PageRenderTime 10ms CodeModel.GetById 1ms app.highlight 5ms RepoModel.GetById 1ms app.codeStats 0ms

/src/luarocks/fetch/git.lua

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