4 from stgit
.config
import config
5 from stgit
.exception
import StackException
6 from stgit
.lib
.git
.objects
import BlobData
, CommitData
, TreeData
7 from stgit
.out
import out
8 from stgit
.run
import RunException
10 # The current StGit metadata format version.
14 def _format_version_key(branch
):
15 return 'branch.%s.stgit.stackformatversion' % branch
18 def _read_strings(filename
):
19 """Reads the lines from a file"""
20 with
open(filename
, encoding
='utf-8') as f
:
21 return [line
.strip() for line
in f
.readlines()]
24 def _read_string(filename
):
25 """Reads the first line from a file"""
26 with
open(filename
, encoding
='utf-8') as f
:
27 return f
.readline().strip()
35 def update_to_current_format_version(repository
, branch
):
36 """Update a potentially older StGit directory structure to the latest version.
38 Note: This function should depend as little as possible on external functions that
39 may change during a format version bump, since it must remain able to process older
44 patches_dir
= os
.path
.join(repository
.directory
, 'patches')
45 branch_dir
= os
.path
.join(patches_dir
, branch
)
46 old_format_key
= _format_version_key(branch
)
47 older_format_key
= 'branch.%s.stgitformatversion' % branch
49 def get_meta_file_version():
50 """Get format version from the ``meta`` file in the stack log branch."""
51 stack_ref
= 'refs/heads/%s.stgit:meta' % branch
54 repository
.run(['git', 'show', stack_ref
])
62 if line
.startswith('Version: '):
63 return int(line
.split('Version: ', 1)[1])
67 def get_format_version():
68 """Return the integer format version number.
70 :returns: the format version number or None if the branch does not have any
71 StGit metadata at all, of any version
74 mfv
= get_meta_file_version()
75 if mfv
is not None and mfv
>= 4:
76 # Modern-era format version found in branch meta blob.
79 # Older format versions were stored in the Git config.
80 fv
= config
.get(old_format_key
)
81 ofv
= config
.get(older_format_key
)
83 # Great, there's an explicitly recorded format version
84 # number, which means that the branch is initialized and
85 # of that exact version.
88 # Old name for the version info: upgrade it.
89 config
.set(old_format_key
, ofv
)
90 config
.unset(older_format_key
)
92 elif os
.path
.isdir(os
.path
.join(branch_dir
, 'patches')):
93 # There's a .git/patches/<branch>/patches dirctory, which
94 # means this is an initialized version 1 branch.
96 elif os
.path
.isdir(branch_dir
):
97 # There's a .git/patches/<branch> directory, which means
98 # this is an initialized version 0 branch.
101 # The branch doesn't seem to be initialized at all.
104 def set_format_version_in_config(v
):
105 out
.info('Upgraded branch %s to format version %d' % (branch
, v
))
106 config
.set(old_format_key
, '%d' % v
)
109 if repository
.refs
.exists(ref
):
110 repository
.refs
.delete(ref
)
113 if get_format_version() == 0:
114 os
.makedirs(os
.path
.join(branch_dir
, 'trash'), exist_ok
=True)
115 patch_dir
= os
.path
.join(branch_dir
, 'patches')
116 os
.makedirs(patch_dir
, exist_ok
=True)
117 refs_base
= 'refs/patches/%s' % branch
118 with
open(os
.path
.join(branch_dir
, 'unapplied')) as f
:
119 patches
= f
.readlines()
120 with
open(os
.path
.join(branch_dir
, 'applied')) as f
:
121 patches
.extend(f
.readlines())
122 for patch
in patches
:
123 patch
= patch
.strip()
124 os
.rename(os
.path
.join(branch_dir
, patch
), os
.path
.join(patch_dir
, patch
))
125 topfield
= os
.path
.join(patch_dir
, patch
, 'top')
126 if os
.path
.isfile(topfield
):
127 top
= _read_string(topfield
)
132 refs_base
+ '/' + patch
,
133 repository
.get_commit(top
),
136 set_format_version_in_config(1)
139 if get_format_version() == 1:
140 desc_file
= os
.path
.join(branch_dir
, 'description')
141 if os
.path
.isfile(desc_file
):
142 desc
= _read_string(desc_file
)
144 config
.set('branch.%s.description' % branch
, desc
)
146 _try_rm(os
.path
.join(branch_dir
, 'current'))
147 rm_ref('refs/bases/%s' % branch
)
148 set_format_version_in_config(2)
151 if get_format_version() == 2:
152 protect_file
= os
.path
.join(branch_dir
, 'protected')
153 if os
.path
.isfile(protect_file
):
154 config
.set('branch.%s.stgit.protect' % branch
, 'true')
155 os
.remove(protect_file
)
156 set_format_version_in_config(3)
158 # compatibility with the new infrastructure. The changes here do not
159 # affect the compatibility with the old infrastructure (format version 2)
160 if get_format_version() == 3:
161 os
.makedirs(branch_dir
, exist_ok
=True)
162 hidden_file
= os
.path
.join(branch_dir
, 'hidden')
163 if not os
.path
.isfile(hidden_file
):
164 open(hidden_file
, 'w+', encoding
='utf-8').close()
166 applied_file
= os
.path
.join(branch_dir
, 'applied')
167 unapplied_file
= os
.path
.join(branch_dir
, 'unapplied')
169 applied
= _read_strings(applied_file
)
170 unapplied
= _read_strings(unapplied_file
)
171 hidden
= _read_strings(hidden_file
)
173 state_ref
= 'refs/heads/%s.stgit' % branch
175 head
= repository
.refs
.get('refs/heads/%s' % branch
)
180 'Head: %s' % head
.sha1
,
185 for patch_list
, title
in [
186 (applied
, 'Applied'),
187 (unapplied
, 'Unapplied'),
190 meta_lines
.append('%s:' % title
)
191 for i
, pn
in enumerate(patch_list
):
192 patch_ref
= 'refs/patches/%s/%s' % (branch
, pn
)
193 commit
= repository
.refs
.get(patch_ref
)
194 meta_lines
.append(' %s: %s' % (pn
, commit
.sha1
))
195 if title
!= 'Applied' or i
== len(patch_list
) - 1:
196 if commit
not in parents
:
197 parents
.append(commit
)
199 patch_meta
= '\n'.join(
201 'Bottom: %s' % cd
.parent
.data
.tree
.sha1
,
202 'Top: %s' % cd
.tree
.sha1
,
203 'Author: %s' % cd
.author
.name_email
,
204 'Date: %s' % cd
.author
.date
,
209 patches_tree
[pn
] = repository
.commit(BlobData(patch_meta
))
210 meta_lines
.append('')
212 meta
= '\n'.join(meta_lines
).encode('utf-8')
213 tree
= repository
.commit(
216 'meta': repository
.commit(BlobData(meta
)),
217 'patches': repository
.commit(TreeData(patches_tree
)),
221 state_commit
= repository
.commit(
224 message
='stack upgrade to version 4',
228 repository
.refs
.set(state_ref
, state_commit
, 'stack upgrade to v4')
230 for patch_list
in [applied
, unapplied
, hidden
]:
231 for pn
in patch_list
:
232 patch_log_ref
= 'refs/patches/%s/%s.log' % (branch
, pn
)
233 if repository
.refs
.exists(patch_log_ref
):
234 repository
.refs
.delete(patch_log_ref
)
236 config
.unset(old_format_key
)
238 shutil
.rmtree(branch_dir
)
240 # .git/patches will be removed after the last stack is converted
241 os
.rmdir(patches_dir
)
244 out
.info('Upgraded branch %s to format version %d' % (branch
, 4))
246 # Make sure we're at the latest version.
247 fv
= get_format_version()
248 if fv
not in [None, FORMAT_VERSION
]:
249 raise StackException(
250 'Branch %s is at format version %d, expected %d'
251 % (branch
, fv
, FORMAT_VERSION
)
253 return fv
is not None # true if branch is initialized