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 mkdir_file(filename
, mode
, encoding
='utf-8'):
19 """Opens filename with the given mode, creating the directory it's
20 in if it doesn't already exist."""
21 os
.makedirs(os
.path
.dirname(filename
), exist_ok
=True)
22 return open(filename
, mode
, encoding
=encoding
)
25 def read_strings(filename
, encoding
='utf-8'):
26 """Reads the lines from a file"""
27 with
open(filename
, encoding
=encoding
) as f
:
28 return [line
.strip() for line
in f
.readlines()]
31 def read_string(filename
, encoding
='utf-8'):
32 """Reads the first line from a file"""
33 with
open(filename
, encoding
=encoding
) as f
:
34 return f
.readline().strip()
37 def create_empty_file(name
):
38 """Creates an empty file"""
39 mkdir_file(name
, 'w+').close()
42 def update_to_current_format_version(repository
, branch
):
43 """Update a potentially older StGit directory structure to the latest
44 version. Note: This function should depend as little as possible
45 on external functions that may change during a format version
46 bump, since it must remain able to process older formats."""
48 patches_dir
= os
.path
.join(repository
.directory
, 'patches')
49 branch_dir
= os
.path
.join(patches_dir
, branch
)
50 old_format_key
= format_version_key(branch
)
51 older_format_key
= 'branch.%s.stgitformatversion' % branch
53 def get_meta_file_version():
54 stack_ref
= 'refs/heads/%s.stgit:meta' % branch
57 repository
.run(['git', 'show', stack_ref
])
65 if line
.startswith('Version: '):
66 return int(line
.split('Version: ', 1)[1])
70 def get_format_version():
71 """Return the integer format version number, or None if the
72 branch doesn't have any StGit metadata at all, of any version."""
73 mfv
= get_meta_file_version()
74 if mfv
is not None and mfv
>= 4:
75 # Modern-era format version found in branch meta blob.
78 # Older format versions were stored in the Git config.
79 fv
= config
.get(old_format_key
)
80 ofv
= config
.get(older_format_key
)
82 # Great, there's an explicitly recorded format version
83 # number, which means that the branch is initialized and
84 # of that exact version.
87 # Old name for the version info: upgrade it.
88 config
.set(old_format_key
, ofv
)
89 config
.unset(older_format_key
)
91 elif os
.path
.isdir(os
.path
.join(branch_dir
, 'patches')):
92 # There's a .git/patches/<branch>/patches dirctory, which
93 # means this is an initialized version 1 branch.
95 elif os
.path
.isdir(branch_dir
):
96 # There's a .git/patches/<branch> directory, which means
97 # this is an initialized version 0 branch.
100 # The branch doesn't seem to be initialized at all.
103 def set_format_version_in_config(v
):
104 out
.info('Upgraded branch %s to format version %d' % (branch
, v
))
105 config
.set(old_format_key
, '%d' % v
)
108 if not os
.path
.isdir(d
):
112 if os
.path
.exists(f
):
116 if repository
.refs
.exists(ref
):
117 repository
.refs
.delete(ref
)
120 if get_format_version() == 0:
121 mkdir(os
.path
.join(branch_dir
, 'trash'))
122 patch_dir
= os
.path
.join(branch_dir
, 'patches')
124 refs_base
= 'refs/patches/%s' % branch
125 with
open(os
.path
.join(branch_dir
, 'unapplied')) as f
:
126 patches
= f
.readlines()
127 with
open(os
.path
.join(branch_dir
, 'applied')) as f
:
128 patches
.extend(f
.readlines())
129 for patch
in patches
:
130 patch
= patch
.strip()
131 os
.rename(os
.path
.join(branch_dir
, patch
), os
.path
.join(patch_dir
, patch
))
132 topfield
= os
.path
.join(patch_dir
, patch
, 'top')
133 if os
.path
.isfile(topfield
):
134 top
= read_string(topfield
)
139 refs_base
+ '/' + patch
,
140 repository
.get_commit(top
),
143 set_format_version_in_config(1)
146 if get_format_version() == 1:
147 desc_file
= os
.path
.join(branch_dir
, 'description')
148 if os
.path
.isfile(desc_file
):
149 desc
= read_string(desc_file
)
151 config
.set('branch.%s.description' % branch
, desc
)
153 rm(os
.path
.join(branch_dir
, 'current'))
154 rm_ref('refs/bases/%s' % branch
)
155 set_format_version_in_config(2)
158 if get_format_version() == 2:
159 protect_file
= os
.path
.join(branch_dir
, 'protected')
160 if os
.path
.isfile(protect_file
):
161 config
.set('branch.%s.stgit.protect' % branch
, 'true')
162 os
.remove(protect_file
)
163 set_format_version_in_config(3)
165 # compatibility with the new infrastructure. The changes here do not
166 # affect the compatibility with the old infrastructure (format version 2)
167 if get_format_version() == 3:
168 hidden_file
= os
.path
.join(branch_dir
, 'hidden')
169 if not os
.path
.isfile(hidden_file
):
170 create_empty_file(hidden_file
)
172 applied_file
= os
.path
.join(branch_dir
, 'applied')
173 unapplied_file
= os
.path
.join(branch_dir
, 'unapplied')
175 applied
= read_strings(applied_file
)
176 unapplied
= read_strings(unapplied_file
)
177 hidden
= read_strings(hidden_file
)
179 state_ref
= 'refs/heads/%s.stgit' % branch
181 head
= repository
.refs
.get('refs/heads/%s' % branch
)
186 'Head: %s' % head
.sha1
,
191 for patch_list
, title
in [
192 (applied
, 'Applied'),
193 (unapplied
, 'Unapplied'),
196 meta_lines
.append('%s:' % title
)
197 for i
, pn
in enumerate(patch_list
):
198 patch_ref
= 'refs/patches/%s/%s' % (branch
, pn
)
199 commit
= repository
.refs
.get(patch_ref
)
200 meta_lines
.append(' %s: %s' % (pn
, commit
.sha1
))
201 if title
!= 'Applied' or i
== len(patch_list
) - 1:
202 if commit
not in parents
:
203 parents
.append(commit
)
205 patch_meta
= '\n'.join(
207 'Bottom: %s' % cd
.parent
.data
.tree
.sha1
,
208 'Top: %s' % cd
.tree
.sha1
,
209 'Author: %s' % cd
.author
.name_email
,
210 'Date: %s' % cd
.author
.date
,
215 patches_tree
[pn
] = repository
.commit(BlobData(patch_meta
))
216 meta_lines
.append('')
218 meta
= '\n'.join(meta_lines
).encode('utf-8')
219 tree
= repository
.commit(
222 'meta': repository
.commit(BlobData(meta
)),
223 'patches': repository
.commit(TreeData(patches_tree
)),
227 state_commit
= repository
.commit(
230 message
='stack upgrade to version 4',
234 repository
.refs
.set(state_ref
, state_commit
, 'stack upgrade to v4')
236 for patch_list
in [applied
, unapplied
, hidden
]:
237 for pn
in patch_list
:
238 patch_log_ref
= 'refs/patches/%s/%s.log' % (branch
, pn
)
239 if repository
.refs
.exists(patch_log_ref
):
240 repository
.refs
.delete(patch_log_ref
)
242 config
.unset(old_format_key
)
244 shutil
.rmtree(branch_dir
)
246 # .git/patches will be removed after the last stack is converted
247 os
.rmdir(patches_dir
)
250 out
.info('Upgraded branch %s to format version %d' % (branch
, 4))
252 # Make sure we're at the latest version.
253 fv
= get_format_version()
254 if fv
not in [None, FORMAT_VERSION
]:
255 raise StackException(
256 'Branch %s is at format version %d, expected %d'
257 % (branch
, fv
, FORMAT_VERSION
)
259 return fv
is not None # true if branch is initialized