5416: Add read-only clone_urls attribute to Repository resources, deprecate push_url...
[arvados.git] / apps / workbench / app / models / repository.rb
1 class Repository < ArvadosBase
2   def self.creatable?
3     current_user and current_user.is_admin
4   end
5   def attributes_for_display
6     super.reject { |x| x[0] == 'fetch_url' }
7   end
8   def editable_attributes
9     if current_user.is_admin
10       super
11     else
12       []
13     end
14   end
15
16   def show commit_sha1
17     refresh
18     run_git 'show', commit_sha1
19   end
20
21   def cat_file commit_sha1, path
22     refresh
23     run_git 'cat-file', 'blob', commit_sha1 + ':' + path
24   end
25
26   def ls_tree_lr commit_sha1
27     refresh
28     run_git 'ls-tree', '-l', '-r', commit_sha1
29   end
30
31   # subtree returns a list of files under the given path at the
32   # specified commit. Results are returned as an array of file nodes,
33   # where each file node is an array [file mode, blob sha1, file size
34   # in bytes, path relative to the given directory]. If the path is
35   # not found, [] is returned.
36   def ls_subtree commit, path
37     path = path.chomp '/'
38     subtree = []
39     ls_tree_lr(commit).each_line do |line|
40       mode, type, sha1, size, filepath = line.split
41       next if type != 'blob'
42       if filepath[0,path.length] == path and
43           (path == '' or filepath[path.length] == '/')
44         subtree << [mode.to_i(8), sha1, size.to_i,
45                     filepath[path.length,filepath.length]]
46       end
47     end
48     subtree
49   end
50
51   protected
52
53   # refresh fetches the latest repository content into the local
54   # cache. It is a no-op if it has already been run on this object:
55   # this (pretty much) avoids doing more than one remote git operation
56   # per Workbench request.
57   def refresh
58     run_git 'fetch', http_fetch_url, '+*:*' unless @fresh
59     @fresh = true
60   end
61
62   # http_fetch_url returns the first http:// or https:// url (if any)
63   # in the api response's clone_urls attribute.
64   def http_fetch_url
65     clone_urls.andand.select { |u| /^http/ =~ u }.first
66   end
67
68   # run_git sets up the ARVADOS_API_TOKEN environment variable,
69   # creates a local git directory for this repository if necessary,
70   # executes "git --git-dir localgitdir {args to run_git}", and
71   # returns the output. It raises GitCommandError if git exits
72   # non-zero.
73   def run_git *gitcmd
74     if not @workdir
75       workdir = File.expand_path uuid+'.git', Rails.configuration.repository_cache
76       if not File.exists? workdir
77         FileUtils.mkdir_p Rails.configuration.repository_cache
78         [['git', 'init', '--bare', workdir],
79         ].each do |cmd|
80           system *cmd
81           raise GitCommandError.new($?.to_s) unless $?.exitstatus == 0
82         end
83       end
84       @workdir = workdir
85     end
86     [['git', '--git-dir', @workdir, 'config', '--local',
87       "credential.#{http_fetch_url}.username", 'none'],
88      ['git', '--git-dir', @workdir, 'config', '--local',
89       "credential.#{http_fetch_url}.helper",
90       '!token(){ echo password="$ARVADOS_API_TOKEN"; }; token'],
91      ['git', '--git-dir', @workdir, 'config', '--local',
92            'http.sslVerify',
93            Rails.configuration.arvados_insecure_https ? 'false' : 'true'],
94      ].each do |cmd|
95       system *cmd
96       raise GitCommandError.new($?.to_s) unless $?.exitstatus == 0
97     end
98     env = {}.
99       merge(ENV).
100       merge('ARVADOS_API_TOKEN' => Thread.current[:arvados_api_token])
101     cmd = ['git', '--git-dir', @workdir] + gitcmd
102     io = IO.popen(env, cmd, err: [:child, :out])
103     output = io.read
104     io.close
105     # "If [io] is opened by IO.popen, close sets $?." --ruby 2.2.1 docs
106     unless $?.exitstatus == 0
107       raise GitCommandError.new("`git #{gitcmd.join ' '}` #{$?}: #{output}")
108     end
109     output
110   end
111
112   class GitCommandError < StandardError
113   end
114 end