##// END OF EJS Templates
gists: fix problem of deleted gists repositories.
marcink -
r2817:ae0b3477 default
parent child Browse files
Show More
@@ -1,250 +1,255 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2018 RhodeCode GmbH
3 # Copyright (C) 2013-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 gist model for RhodeCode
22 gist model for RhodeCode
23 """
23 """
24
24
25 import os
25 import os
26 import time
26 import time
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import shutil
29 import shutil
30
30
31 from pyramid.threadlocal import get_current_request
31 from pyramid.threadlocal import get_current_request
32
32
33 from rhodecode.lib.utils2 import (
33 from rhodecode.lib.utils2 import (
34 safe_unicode, unique_id, safe_int, time_to_datetime, AttributeDict)
34 safe_unicode, unique_id, safe_int, time_to_datetime, AttributeDict)
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.vcs import VCSError
36 from rhodecode.model import BaseModel
37 from rhodecode.model import BaseModel
37 from rhodecode.model.db import Gist
38 from rhodecode.model.db import Gist
38 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.scm import ScmModel
40
41
41 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
42
43
43 GIST_STORE_LOC = '.rc_gist_store'
44 GIST_STORE_LOC = '.rc_gist_store'
44 GIST_METADATA_FILE = '.rc_gist_metadata'
45 GIST_METADATA_FILE = '.rc_gist_metadata'
45
46
46
47
47 class GistModel(BaseModel):
48 class GistModel(BaseModel):
48 cls = Gist
49 cls = Gist
49
50
50 def _get_gist(self, gist):
51 def _get_gist(self, gist):
51 """
52 """
52 Helper method to get gist by ID, or gist_access_id as a fallback
53 Helper method to get gist by ID, or gist_access_id as a fallback
53
54
54 :param gist: GistID, gist_access_id, or Gist instance
55 :param gist: GistID, gist_access_id, or Gist instance
55 """
56 """
56 return self._get_instance(Gist, gist, callback=Gist.get_by_access_id)
57 return self._get_instance(Gist, gist, callback=Gist.get_by_access_id)
57
58
58 def __delete_gist(self, gist):
59 def __delete_gist(self, gist):
59 """
60 """
60 removes gist from filesystem
61 removes gist from filesystem
61
62
62 :param gist: gist object
63 :param gist: gist object
63 """
64 """
64 root_path = RepoModel().repos_path
65 root_path = RepoModel().repos_path
65 rm_path = os.path.join(root_path, GIST_STORE_LOC, gist.gist_access_id)
66 rm_path = os.path.join(root_path, GIST_STORE_LOC, gist.gist_access_id)
66 log.info("Removing %s", rm_path)
67 log.info("Removing %s", rm_path)
67 shutil.rmtree(rm_path)
68 shutil.rmtree(rm_path)
68
69
69 def _store_metadata(self, repo, gist_id, gist_access_id, user_id, username,
70 def _store_metadata(self, repo, gist_id, gist_access_id, user_id, username,
70 gist_type, gist_expires, gist_acl_level):
71 gist_type, gist_expires, gist_acl_level):
71 """
72 """
72 store metadata inside the gist repo, this can be later used for imports
73 store metadata inside the gist repo, this can be later used for imports
73 or gist identification. Currently we use this inside RhodeCode tools
74 or gist identification. Currently we use this inside RhodeCode tools
74 to do cleanup of gists that are in storage but not in database.
75 to do cleanup of gists that are in storage but not in database.
75 """
76 """
76 metadata = {
77 metadata = {
77 'metadata_version': '2',
78 'metadata_version': '2',
78 'gist_db_id': gist_id,
79 'gist_db_id': gist_id,
79 'gist_access_id': gist_access_id,
80 'gist_access_id': gist_access_id,
80 'gist_owner_id': user_id,
81 'gist_owner_id': user_id,
81 'gist_owner_username': username,
82 'gist_owner_username': username,
82 'gist_type': gist_type,
83 'gist_type': gist_type,
83 'gist_expires': gist_expires,
84 'gist_expires': gist_expires,
84 'gist_updated': time.time(),
85 'gist_updated': time.time(),
85 'gist_acl_level': gist_acl_level,
86 'gist_acl_level': gist_acl_level,
86 }
87 }
87 metadata_file = os.path.join(repo.path, '.hg', GIST_METADATA_FILE)
88 metadata_file = os.path.join(repo.path, '.hg', GIST_METADATA_FILE)
88 with open(metadata_file, 'wb') as f:
89 with open(metadata_file, 'wb') as f:
89 f.write(json.dumps(metadata))
90 f.write(json.dumps(metadata))
90
91
91 def get_gist(self, gist):
92 def get_gist(self, gist):
92 return self._get_gist(gist)
93 return self._get_gist(gist)
93
94
94 def get_gist_files(self, gist_access_id, revision=None):
95 def get_gist_files(self, gist_access_id, revision=None):
95 """
96 """
96 Get files for given gist
97 Get files for given gist
97
98
98 :param gist_access_id:
99 :param gist_access_id:
99 """
100 """
100 repo = Gist.get_by_access_id(gist_access_id)
101 repo = Gist.get_by_access_id(gist_access_id)
101 commit = repo.scm_instance().get_commit(commit_id=revision)
102 vcs_repo = repo.scm_instance()
103 if not vcs_repo:
104 raise VCSError('Failed to load gist repository for {}'.format(repo))
105
106 commit = vcs_repo.get_commit(commit_id=revision)
102 return commit, [n for n in commit.get_node('/')]
107 return commit, [n for n in commit.get_node('/')]
103
108
104 def create(self, description, owner, gist_mapping,
109 def create(self, description, owner, gist_mapping,
105 gist_type=Gist.GIST_PUBLIC, lifetime=-1, gist_id=None,
110 gist_type=Gist.GIST_PUBLIC, lifetime=-1, gist_id=None,
106 gist_acl_level=Gist.ACL_LEVEL_PRIVATE):
111 gist_acl_level=Gist.ACL_LEVEL_PRIVATE):
107 """
112 """
108 Create a gist
113 Create a gist
109
114
110 :param description: description of the gist
115 :param description: description of the gist
111 :param owner: user who created this gist
116 :param owner: user who created this gist
112 :param gist_mapping: mapping [{'filename': 'file1.txt', 'content': content}, ...}]
117 :param gist_mapping: mapping [{'filename': 'file1.txt', 'content': content}, ...}]
113 :param gist_type: type of gist private/public
118 :param gist_type: type of gist private/public
114 :param lifetime: in minutes, -1 == forever
119 :param lifetime: in minutes, -1 == forever
115 :param gist_acl_level: acl level for this gist
120 :param gist_acl_level: acl level for this gist
116 """
121 """
117 owner = self._get_user(owner)
122 owner = self._get_user(owner)
118 gist_id = safe_unicode(gist_id or unique_id(20))
123 gist_id = safe_unicode(gist_id or unique_id(20))
119 lifetime = safe_int(lifetime, -1)
124 lifetime = safe_int(lifetime, -1)
120 gist_expires = time.time() + (lifetime * 60) if lifetime != -1 else -1
125 gist_expires = time.time() + (lifetime * 60) if lifetime != -1 else -1
121 expiration = (time_to_datetime(gist_expires)
126 expiration = (time_to_datetime(gist_expires)
122 if gist_expires != -1 else 'forever')
127 if gist_expires != -1 else 'forever')
123 log.debug('set GIST expiration date to: %s', expiration)
128 log.debug('set GIST expiration date to: %s', expiration)
124 # create the Database version
129 # create the Database version
125 gist = Gist()
130 gist = Gist()
126 gist.gist_description = description
131 gist.gist_description = description
127 gist.gist_access_id = gist_id
132 gist.gist_access_id = gist_id
128 gist.gist_owner = owner.user_id
133 gist.gist_owner = owner.user_id
129 gist.gist_expires = gist_expires
134 gist.gist_expires = gist_expires
130 gist.gist_type = safe_unicode(gist_type)
135 gist.gist_type = safe_unicode(gist_type)
131 gist.acl_level = gist_acl_level
136 gist.acl_level = gist_acl_level
132 self.sa.add(gist)
137 self.sa.add(gist)
133 self.sa.flush()
138 self.sa.flush()
134 if gist_type == Gist.GIST_PUBLIC:
139 if gist_type == Gist.GIST_PUBLIC:
135 # use DB ID for easy to use GIST ID
140 # use DB ID for easy to use GIST ID
136 gist_id = safe_unicode(gist.gist_id)
141 gist_id = safe_unicode(gist.gist_id)
137 gist.gist_access_id = gist_id
142 gist.gist_access_id = gist_id
138 self.sa.add(gist)
143 self.sa.add(gist)
139
144
140 gist_repo_path = os.path.join(GIST_STORE_LOC, gist_id)
145 gist_repo_path = os.path.join(GIST_STORE_LOC, gist_id)
141 log.debug('Creating new %s GIST repo in %s', gist_type, gist_repo_path)
146 log.debug('Creating new %s GIST repo in %s', gist_type, gist_repo_path)
142 repo = RepoModel()._create_filesystem_repo(
147 repo = RepoModel()._create_filesystem_repo(
143 repo_name=gist_id, repo_type='hg', repo_group=GIST_STORE_LOC,
148 repo_name=gist_id, repo_type='hg', repo_group=GIST_STORE_LOC,
144 use_global_config=True)
149 use_global_config=True)
145
150
146 # now create single multifile commit
151 # now create single multifile commit
147 message = 'added file'
152 message = 'added file'
148 message += 's: ' if len(gist_mapping) > 1 else ': '
153 message += 's: ' if len(gist_mapping) > 1 else ': '
149 message += ', '.join([x for x in gist_mapping])
154 message += ', '.join([x for x in gist_mapping])
150
155
151 # fake RhodeCode Repository object
156 # fake RhodeCode Repository object
152 fake_repo = AttributeDict({
157 fake_repo = AttributeDict({
153 'repo_name': gist_repo_path,
158 'repo_name': gist_repo_path,
154 'scm_instance': lambda *args, **kwargs: repo,
159 'scm_instance': lambda *args, **kwargs: repo,
155 })
160 })
156
161
157 ScmModel().create_nodes(
162 ScmModel().create_nodes(
158 user=owner.user_id, repo=fake_repo,
163 user=owner.user_id, repo=fake_repo,
159 message=message,
164 message=message,
160 nodes=gist_mapping,
165 nodes=gist_mapping,
161 trigger_push_hook=False
166 trigger_push_hook=False
162 )
167 )
163
168
164 self._store_metadata(repo, gist.gist_id, gist.gist_access_id,
169 self._store_metadata(repo, gist.gist_id, gist.gist_access_id,
165 owner.user_id, owner.username, gist.gist_type,
170 owner.user_id, owner.username, gist.gist_type,
166 gist.gist_expires, gist_acl_level)
171 gist.gist_expires, gist_acl_level)
167 return gist
172 return gist
168
173
169 def delete(self, gist, fs_remove=True):
174 def delete(self, gist, fs_remove=True):
170 gist = self._get_gist(gist)
175 gist = self._get_gist(gist)
171 try:
176 try:
172 self.sa.delete(gist)
177 self.sa.delete(gist)
173 if fs_remove:
178 if fs_remove:
174 self.__delete_gist(gist)
179 self.__delete_gist(gist)
175 else:
180 else:
176 log.debug('skipping removal from filesystem')
181 log.debug('skipping removal from filesystem')
177 except Exception:
182 except Exception:
178 log.error(traceback.format_exc())
183 log.error(traceback.format_exc())
179 raise
184 raise
180
185
181 def update(self, gist, description, owner, gist_mapping, lifetime,
186 def update(self, gist, description, owner, gist_mapping, lifetime,
182 gist_acl_level):
187 gist_acl_level):
183 gist = self._get_gist(gist)
188 gist = self._get_gist(gist)
184 gist_repo = gist.scm_instance()
189 gist_repo = gist.scm_instance()
185
190
186 if lifetime == 0: # preserve old value
191 if lifetime == 0: # preserve old value
187 gist_expires = gist.gist_expires
192 gist_expires = gist.gist_expires
188 else:
193 else:
189 gist_expires = (
194 gist_expires = (
190 time.time() + (lifetime * 60) if lifetime != -1 else -1)
195 time.time() + (lifetime * 60) if lifetime != -1 else -1)
191
196
192 # calculate operation type based on given data
197 # calculate operation type based on given data
193 gist_mapping_op = {}
198 gist_mapping_op = {}
194 for k, v in gist_mapping.items():
199 for k, v in gist_mapping.items():
195 # add, mod, del
200 # add, mod, del
196 if not v['filename_org'] and v['filename']:
201 if not v['filename_org'] and v['filename']:
197 op = 'add'
202 op = 'add'
198 elif v['filename_org'] and not v['filename']:
203 elif v['filename_org'] and not v['filename']:
199 op = 'del'
204 op = 'del'
200 else:
205 else:
201 op = 'mod'
206 op = 'mod'
202
207
203 v['op'] = op
208 v['op'] = op
204 gist_mapping_op[k] = v
209 gist_mapping_op[k] = v
205
210
206 gist.gist_description = description
211 gist.gist_description = description
207 gist.gist_expires = gist_expires
212 gist.gist_expires = gist_expires
208 gist.owner = owner
213 gist.owner = owner
209 gist.acl_level = gist_acl_level
214 gist.acl_level = gist_acl_level
210 self.sa.add(gist)
215 self.sa.add(gist)
211 self.sa.flush()
216 self.sa.flush()
212
217
213 message = 'updated file'
218 message = 'updated file'
214 message += 's: ' if len(gist_mapping) > 1 else ': '
219 message += 's: ' if len(gist_mapping) > 1 else ': '
215 message += ', '.join([x for x in gist_mapping])
220 message += ', '.join([x for x in gist_mapping])
216
221
217 # fake RhodeCode Repository object
222 # fake RhodeCode Repository object
218 fake_repo = AttributeDict({
223 fake_repo = AttributeDict({
219 'repo_name': gist_repo.path,
224 'repo_name': gist_repo.path,
220 'scm_instance': lambda *args, **kwargs: gist_repo,
225 'scm_instance': lambda *args, **kwargs: gist_repo,
221 })
226 })
222
227
223 self._store_metadata(gist_repo, gist.gist_id, gist.gist_access_id,
228 self._store_metadata(gist_repo, gist.gist_id, gist.gist_access_id,
224 owner.user_id, owner.username, gist.gist_type,
229 owner.user_id, owner.username, gist.gist_type,
225 gist.gist_expires, gist_acl_level)
230 gist.gist_expires, gist_acl_level)
226
231
227 # this can throw NodeNotChangedError, if changes we're trying to commit
232 # this can throw NodeNotChangedError, if changes we're trying to commit
228 # are not actually changes...
233 # are not actually changes...
229 ScmModel().update_nodes(
234 ScmModel().update_nodes(
230 user=owner.user_id,
235 user=owner.user_id,
231 repo=fake_repo,
236 repo=fake_repo,
232 message=message,
237 message=message,
233 nodes=gist_mapping_op,
238 nodes=gist_mapping_op,
234 trigger_push_hook=False
239 trigger_push_hook=False
235 )
240 )
236
241
237 return gist
242 return gist
238
243
239 def get_url(self, gist, request=None):
244 def get_url(self, gist, request=None):
240 import rhodecode
245 import rhodecode
241
246
242 if not request:
247 if not request:
243 request = get_current_request()
248 request = get_current_request()
244
249
245 alias_url = rhodecode.CONFIG.get('gist_alias_url')
250 alias_url = rhodecode.CONFIG.get('gist_alias_url')
246 if alias_url:
251 if alias_url:
247 return alias_url.replace('{gistid}', gist.gist_access_id)
252 return alias_url.replace('{gistid}', gist.gist_access_id)
248
253
249 return request.route_url('gist_show', gist_id=gist.gist_access_id)
254 return request.route_url('gist_show', gist_id=gist.gist_access_id)
250
255
General Comments 0
You need to be logged in to leave comments. Login now