5 from stgit
.config
import config
6 from stgit
.exception
import StackException
7 from stgit
.lib
.git
.objects
import BlobData
, CommitData
, TreeData
8 from stgit
.out
import out
9 from stgit
.run
import RunException
11 # The current StGit metadata format version.
15 def _format_version_key(branch
):
16 return 'branch.%s.stgit.stackformatversion' % branch
19 def _read_strings(filename
):
20 """Reads the lines from a file"""
21 with
open(filename
, encoding
='utf-8') as f
:
22 return [line
.strip() for line
in f
.readlines()]
25 def _read_string(filename
):
26 """Reads the first line from a file"""
27 with
open(filename
, encoding
='utf-8') as f
:
28 return f
.readline().strip()
36 def update_to_current_format_version(repository
, branch
):
37 """Update a potentially older StGit directory structure to the latest version.
39 Note: This function should depend as little as possible on external functions that
40 may change during a format version bump, since it must remain able to process older
45 patches_dir
= os
.path
.join(repository
.directory
, 'patches')
46 branch_dir
= os
.path
.join(patches_dir
, branch
)
47 old_format_key
= _format_version_key(branch
)
48 older_format_key
= 'branch.%s.stgitformatversion' % branch
50 def get_meta_file_version():
51 """Get format version from the ``meta`` file in the stack log branch."""
52 new_version
= get_stack_json_file_version()
53 if new_version
is not None:
56 old_version
= get_old_meta_file_version()
57 if old_version
is not None:
60 def get_old_meta_file_version():
61 """Get format version from the ``meta`` file in the stack log branch."""
62 stack_ref
= 'refs/heads/%s.stgit:meta' % branch
65 repository
.run(['git', 'show', stack_ref
])
73 if line
.startswith('Version: '):
74 return int(line
.split('Version: ', 1)[1])
78 def get_stack_json_file_version():
79 stack_ref
= 'refs/stacks/%s:stack.json' % branch
82 repository
.run(['git', 'show', stack_ref
])
91 stack_json
= json
.loads(data
)
92 except json
.JSONDecodeError
:
95 return stack_json
.get('version')
97 def get_format_version():
98 """Return the integer format version number.
100 :returns: the format version number or None if the branch does not have any
101 StGit metadata at all, of any version
104 mfv
= get_meta_file_version()
105 if mfv
is not None and mfv
>= 4:
106 # Modern-era format version found in branch meta blob.
109 # Older format versions were stored in the Git config.
110 fv
= config
.get(old_format_key
)
111 ofv
= config
.get(older_format_key
)
113 # Great, there's an explicitly recorded format version
114 # number, which means that the branch is initialized and
115 # of that exact version.
118 # Old name for the version info: upgrade it.
119 config
.set(old_format_key
, ofv
)
120 config
.unset(older_format_key
)
122 elif os
.path
.isdir(os
.path
.join(branch_dir
, 'patches')):
123 # There's a .git/patches/<branch>/patches dirctory, which
124 # means this is an initialized version 1 branch.
126 elif os
.path
.isdir(branch_dir
):
127 # There's a .git/patches/<branch> directory, which means
128 # this is an initialized version 0 branch.
131 # The branch doesn't seem to be initialized at all.
134 def set_format_version_in_config(v
):
135 out
.info('Upgraded branch %s to format version %d' % (branch
, v
))
136 config
.set(old_format_key
, '%d' % v
)
139 if repository
.refs
.exists(ref
):
140 repository
.refs
.delete(ref
)
143 if get_format_version() == 0:
144 os
.makedirs(os
.path
.join(branch_dir
, 'trash'), exist_ok
=True)
145 patch_dir
= os
.path
.join(branch_dir
, 'patches')
146 os
.makedirs(patch_dir
, exist_ok
=True)
147 refs_base
= 'refs/patches/%s' % branch
148 with
open(os
.path
.join(branch_dir
, 'unapplied')) as f
:
149 patches
= f
.readlines()
150 with
open(os
.path
.join(branch_dir
, 'applied')) as f
:
151 patches
.extend(f
.readlines())
152 for patch
in patches
:
153 patch
= patch
.strip()
154 os
.rename(os
.path
.join(branch_dir
, patch
), os
.path
.join(patch_dir
, patch
))
155 topfield
= os
.path
.join(patch_dir
, patch
, 'top')
156 if os
.path
.isfile(topfield
):
157 top
= _read_string(topfield
)
162 refs_base
+ '/' + patch
,
163 repository
.get_commit(top
),
166 set_format_version_in_config(1)
169 if get_format_version() == 1:
170 desc_file
= os
.path
.join(branch_dir
, 'description')
171 if os
.path
.isfile(desc_file
):
172 desc
= _read_string(desc_file
)
174 config
.set('branch.%s.description' % branch
, desc
)
176 _try_rm(os
.path
.join(branch_dir
, 'current'))
177 rm_ref('refs/bases/%s' % branch
)
178 set_format_version_in_config(2)
181 if get_format_version() == 2:
182 protect_file
= os
.path
.join(branch_dir
, 'protected')
183 if os
.path
.isfile(protect_file
):
184 config
.set('branch.%s.stgit.protect' % branch
, 'true')
185 os
.remove(protect_file
)
186 set_format_version_in_config(3)
188 # compatibility with the new infrastructure. The changes here do not
189 # affect the compatibility with the old infrastructure (format version 2)
190 if get_format_version() == 3:
191 os
.makedirs(branch_dir
, exist_ok
=True)
192 hidden_file
= os
.path
.join(branch_dir
, 'hidden')
193 if not os
.path
.isfile(hidden_file
):
194 open(hidden_file
, 'w+', encoding
='utf-8').close()
196 applied_file
= os
.path
.join(branch_dir
, 'applied')
197 unapplied_file
= os
.path
.join(branch_dir
, 'unapplied')
199 applied
= _read_strings(applied_file
)
200 unapplied
= _read_strings(unapplied_file
)
201 hidden
= _read_strings(hidden_file
)
203 state_ref
= 'refs/heads/%s.stgit' % branch
205 head
= repository
.refs
.get('refs/heads/%s' % branch
)
210 'Head: %s' % head
.sha1
,
215 for patch_list
, title
in [
216 (applied
, 'Applied'),
217 (unapplied
, 'Unapplied'),
220 meta_lines
.append('%s:' % title
)
221 for i
, pn
in enumerate(patch_list
):
222 patch_ref
= 'refs/patches/%s/%s' % (branch
, pn
)
223 commit
= repository
.refs
.get(patch_ref
)
224 meta_lines
.append(' %s: %s' % (pn
, commit
.sha1
))
225 if title
!= 'Applied' or i
== len(patch_list
) - 1:
226 if commit
not in parents
:
227 parents
.append(commit
)
229 patch_meta
= '\n'.join(
231 'Bottom: %s' % cd
.parent
.data
.tree
.sha1
,
232 'Top: %s' % cd
.tree
.sha1
,
233 'Author: %s' % cd
.author
.name_email
,
234 'Date: %s' % cd
.author
.date
,
239 patches_tree
[pn
] = repository
.commit(BlobData(patch_meta
))
240 meta_lines
.append('')
242 meta
= '\n'.join(meta_lines
).encode('utf-8')
243 tree
= repository
.commit(
246 'meta': repository
.commit(BlobData(meta
)),
247 'patches': repository
.commit(TreeData(patches_tree
)),
251 state_commit
= repository
.commit(
254 message
='stack upgrade to version 4',
258 repository
.refs
.set(state_ref
, state_commit
, 'stack upgrade to v4')
260 for patch_list
in [applied
, unapplied
, hidden
]:
261 for pn
in patch_list
:
262 patch_log_ref
= 'refs/patches/%s/%s.log' % (branch
, pn
)
263 if repository
.refs
.exists(patch_log_ref
):
264 repository
.refs
.delete(patch_log_ref
)
266 config
.unset(old_format_key
)
268 shutil
.rmtree(branch_dir
)
270 # .git/patches will be removed after the last stack is converted
271 os
.rmdir(patches_dir
)
274 out
.info('Upgraded branch %s to format version %d' % (branch
, 4))
276 # Metadata moves from refs/heads/<branch>.stgit to refs/stacks/<branch>.
277 # Also, metadata file format is JSON instead of custom format.
278 if get_format_version() == 4:
279 old_state_ref
= 'refs/heads/%s.stgit' % branch
280 old_state
= repository
.refs
.get(old_state_ref
)
281 old_meta
= old_state
.data
.tree
.data
['meta'][1].data
.bytes
282 lines
= old_meta
.decode('utf-8').splitlines()
283 if not lines
[0].startswith('Version: 4'):
284 raise StackException('Malformed metadata (expected version 4)')
289 if line
.startswith(' '):
290 assert key
is not None
291 parsed
[key
].append(line
.strip())
293 key
, val
= [x
.strip() for x
in line
.split(':', 1)]
299 head
= repository
.refs
.get('refs/heads/%s' % branch
)
303 prev
=parsed
['Previous'],
311 if parsed
['Head'] != new_meta
['head']:
312 raise StackException('Unexpected head mismatch')
314 for patch_list_name
in ['Applied', 'Unapplied', 'Hidden']:
315 for entry
in parsed
[patch_list_name
]:
316 pn
, sha1
= [x
.strip() for x
in entry
.split(':')]
317 new_patch_list_name
= patch_list_name
.lower()
318 new_meta
[new_patch_list_name
].append(pn
)
319 new_meta
['patches'][pn
] = dict(oid
=sha1
)
321 meta_bytes
= json
.dumps(new_meta
, indent
=2).encode('utf-8')
323 tree
= repository
.commit(
326 'stack.json': repository
.commit(BlobData(meta_bytes
)),
327 'patches': old_state
.data
.tree
.data
['patches'],
333 'refs/stacks/%s' % branch
,
337 message
='stack upgrade to version 5',
341 'stack upgrade to v5',
344 repository
.refs
.delete(old_state_ref
)
345 out
.info('Upgraded branch %s to format version %d' % (branch
, 5))
347 # Make sure we're at the latest version.
348 fv
= get_format_version()
349 if fv
not in [None, FORMAT_VERSION
]:
350 raise StackException(
351 'Branch %s is at format version %d, expected %d'
352 % (branch
, fv
, FORMAT_VERSION
)
354 return fv
is not None # true if branch is initialized