Merge branch 'master' into 5720-ajax-loading-error
[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   # git 2.1.4 does not use credential helpers reliably, see #5416
52   def self.disable_repository_browsing?
53     return false if Rails.configuration.use_git2_despite_bug_risk
54     if @buggy_git_version.nil?
55       @buggy_git_version = /git version 2/ =~ `git version`
56     end
57     @buggy_git_version
58   end
59
60   # http_fetch_url returns the first http:// or https:// url (if any)
61   # in the api response's clone_urls attribute.
62   def http_fetch_url
63     clone_urls.andand.select { |u| /^http/ =~ u }.first
64   end
65
66   protected
67
68   # refresh fetches the latest repository content into the local
69   # cache. It is a no-op if it has already been run on this object:
70   # this (pretty much) avoids doing more than one remote git operation
71   # per Workbench request.
72   def refresh
73     run_git 'fetch', http_fetch_url, '+*:*' unless @fresh
74     @fresh = true
75   end
76
77   # run_git sets up the ARVADOS_API_TOKEN environment variable,
78   # creates a local git directory for this repository if necessary,
79   # executes "git --git-dir localgitdir {args to run_git}", and
80   # returns the output. It raises GitCommandError if git exits
81   # non-zero.
82   def run_git *gitcmd
83     if not @workdir
84       workdir = File.expand_path uuid+'.git', Rails.configuration.repository_cache
85       if not File.exists? workdir
86         FileUtils.mkdir_p Rails.configuration.repository_cache
87         [['git', 'init', '--bare', workdir],
88         ].each do |cmd|
89           system *cmd
90           raise GitCommandError.new($?.to_s) unless $?.exitstatus == 0
91         end
92       end
93       @workdir = workdir
94     end
95     [['git', '--git-dir', @workdir, 'config', '--local',
96       "credential.#{http_fetch_url}.username", 'none'],
97      ['git', '--git-dir', @workdir, 'config', '--local',
98       "credential.#{http_fetch_url}.helper",
99       '!token(){ echo password="$ARVADOS_API_TOKEN"; }; token'],
100      ['git', '--git-dir', @workdir, 'config', '--local',
101            'http.sslVerify',
102            Rails.configuration.arvados_insecure_https ? 'false' : 'true'],
103      ].each do |cmd|
104       system *cmd
105       raise GitCommandError.new($?.to_s) unless $?.exitstatus == 0
106     end
107     env = {}.
108       merge(ENV).
109       merge('ARVADOS_API_TOKEN' => Thread.current[:arvados_api_token])
110     cmd = ['git', '--git-dir', @workdir] + gitcmd
111     io = IO.popen(env, cmd, err: [:child, :out])
112     output = io.read
113     io.close
114     # "If [io] is opened by IO.popen, close sets $?." --ruby 2.2.1 docs
115     unless $?.exitstatus == 0
116       raise GitCommandError.new("`git #{gitcmd.join ' '}` #{$?}: #{output}")
117     end
118     output
119   end
120
121   class GitCommandError < StandardError
122   end
123 end