1 """A Python class hierarchy wrapping the StGit on-disk metadata."""
4 from stgit
import exception
, utils
5 from stgit
.lib
import git
, stackupgrade
6 from stgit
.config
import config
8 class StackException(exception
.StgException
):
9 """Exception raised by L{stack} objects."""
12 """Represents an StGit patch. This class is mainly concerned with
13 reading and writing the on-disk representation of a patch."""
14 def __init__(self
, stack
, name
):
17 name
= property(lambda self
: self
.__name
)
20 return 'refs/patches/%s/%s' %
(self
.__stack
.name
, self
.__name
)
23 return self
.__ref
+ '.log'
26 return self
.__stack
.repository
.refs
.get(self
.__ref
)
29 """Return the previous commit for this patch."""
30 fn
= os
.path
.join(self
.__compat_dir
, 'top.old')
31 if not os
.path
.isfile(fn
):
33 return self
.__stack
.repository
.get_commit(utils
.read_string(fn
))
35 def __compat_dir(self
):
36 return os
.path
.join(self
.__stack
.directory
, 'patches', self
.__name
)
37 def __write_compat_files(self
, new_commit
, msg
):
38 """Write files used by the old infrastructure."""
39 def write(name
, val
, multiline
= False):
40 fn
= os
.path
.join(self
.__compat_dir
, name
)
42 utils
.write_string(fn
, val
, multiline
)
43 elif os
.path
.isfile(fn
):
47 old_log
= [self
.__stack
.repository
.refs
.get(self
.__log_ref
)]
50 cd
= git
.CommitData(tree
= new_commit
.data
.tree
, parents
= old_log
,
51 message
= '%s\t%s' %
(msg
, new_commit
.sha1
))
52 c
= self
.__stack
.repository
.commit(cd
)
53 self
.__stack
.repository
.refs
.set(self
.__log_ref
, c
, msg
)
56 write('authname', d
.author
.name
)
57 write('authemail', d
.author
.email
)
58 write('authdate', d
.author
.date
)
59 write('commname', d
.committer
.name
)
60 write('commemail', d
.committer
.email
)
61 write('description', d
.message
, multiline
= True)
62 write('log', write_patchlog().sha1
)
63 write('top', new_commit
.sha1
)
64 write('bottom', d
.parent
.sha1
)
66 old_top_sha1
= self
.commit
.sha1
67 old_bottom_sha1
= self
.commit
.data
.parent
.sha1
70 old_bottom_sha1
= None
71 write('top.old', old_top_sha1
)
72 write('bottom.old', old_bottom_sha1
)
73 def __delete_compat_files(self
):
74 if os
.path
.isdir(self
.__compat_dir
):
75 for f
in os
.listdir(self
.__compat_dir
):
76 os
.remove(os
.path
.join(self
.__compat_dir
, f
))
77 os
.rmdir(self
.__compat_dir
)
79 # this compatibility log ref might not exist
80 self
.__stack
.repository
.refs
.delete(self
.__log_ref
)
83 def set_commit(self
, commit
, msg
):
84 self
.__write_compat_files(commit
, msg
)
85 self
.__stack
.repository
.refs
.set(self
.__ref
, commit
, msg
)
87 self
.__delete_compat_files()
88 self
.__stack
.repository
.refs
.delete(self
.__ref
)
90 return self
.name
in self
.__stack
.patchorder
.applied
92 return self
.commit
.data
.is_nochange()
94 """Return the set of files this patch touches."""
96 for (_
, _
, _
, _
, _
, oldname
, newname
97 ) in self
.__stack
.repository
.diff_tree_files(
98 self
.commit
.data
.tree
, self
.commit
.data
.parent
.data
.tree
):
103 class PatchOrder(object):
104 """Keeps track of patch order, and which patches are applied.
105 Works with patch names, not actual patches."""
106 def __init__(self
, stack
):
109 def __read_file(self
, fn
):
110 return tuple(utils
.read_strings(
111 os
.path
.join(self
.__stack
.directory
, fn
)))
112 def __write_file(self
, fn
, val
):
113 utils
.write_strings(os
.path
.join(self
.__stack
.directory
, fn
), val
)
114 def __get_list(self
, name
):
115 if not name
in self
.__lists
:
116 self
.__lists
[name
] = self
.__read_file(name
)
117 return self
.__lists
[name
]
118 def __set_list(self
, name
, val
):
120 if val
!= self
.__lists
.get(name
, None):
121 self
.__lists
[name
] = val
122 self
.__write_file(name
, val
)
123 applied
= property(lambda self
: self
.__get_list('applied'),
124 lambda self
, val
: self
.__set_list('applied', val
))
125 unapplied
= property(lambda self
: self
.__get_list('unapplied'),
126 lambda self
, val
: self
.__set_list('unapplied', val
))
127 hidden
= property(lambda self
: self
.__get_list('hidden'),
128 lambda self
, val
: self
.__set_list('hidden', val
))
129 all
= property(lambda self
: self
.applied
+ self
.unapplied
+ self
.hidden
)
130 all_visible
= property(lambda self
: self
.applied
+ self
.unapplied
)
133 def create(stackdir
):
134 """Create the PatchOrder specific files
136 utils
.create_empty_file(os
.path
.join(stackdir
, 'applied'))
137 utils
.create_empty_file(os
.path
.join(stackdir
, 'unapplied'))
138 utils
.create_empty_file(os
.path
.join(stackdir
, 'hidden'))
140 class Patches(object):
141 """Creates L{Patch} objects. Makes sure there is only one such object
143 def __init__(self
, stack
):
145 def create_patch(name
):
146 p
= Patch(self
.__stack
, name
)
147 p
.commit
# raise exception if the patch doesn't exist
149 self
.__patches
= git
.ObjectCache(create_patch
) # name -> Patch
150 def exists(self
, name
):
157 return self
.__patches
[name
]
158 def new(self
, name
, commit
, msg
):
159 assert not name
in self
.__patches
160 p
= Patch(self
.__stack
, name
)
161 p
.set_commit(commit
, msg
)
162 self
.__patches
[name
] = p
165 class Stack(git
.Branch
):
166 """Represents an StGit stack (that is, a git branch with some extra
168 __repo_subdir
= 'patches'
170 def __init__(self
, repository
, name
):
171 git
.Branch
.__init__(self
, repository
, name
)
172 self
.__patchorder
= PatchOrder(self
)
173 self
.__patches
= Patches(self
)
174 if not stackupgrade
.update_to_current_format_version(repository
, name
):
175 raise StackException('%s: branch not initialized' % name
)
176 patchorder
= property(lambda self
: self
.__patchorder
)
177 patches
= property(lambda self
: self
.__patches
)
180 return os
.path
.join(self
.repository
.directory
, self
.__repo_subdir
, self
.name
)
183 if self
.patchorder
.applied
:
184 return self
.patches
.get(self
.patchorder
.applied
[0]
190 """Commit of the topmost patch, or the stack base if no patches are
192 if self
.patchorder
.applied
:
193 return self
.patches
.get(self
.patchorder
.applied
[-1]).commit
195 # When no patches are applied, base == head.
197 def head_top_equal(self
):
198 if not self
.patchorder
.applied
:
200 return self
.head
== self
.patches
.get(self
.patchorder
.applied
[-1]).commit
202 def set_parents(self
, remote
, branch
):
204 self
.set_parent_remote(remote
)
206 self
.set_parent_branch(branch
)
209 def initialise(cls
, repository
, name
= None):
210 """Initialise a Git branch to handle patch series.
212 @param repository: The L{Repository} where the L{Stack} will be created
213 @param name: The name of the L{Stack}
216 name
= repository
.current_branch_name
217 # make sure that the corresponding Git branch exists
218 git
.Branch(repository
, name
)
220 dir = os
.path
.join(repository
.directory
, cls
.__repo_subdir
, name
)
221 compat_dir
= os
.path
.join(dir, 'patches')
222 if os
.path
.exists(dir):
223 raise StackException('%s: branch already initialized' % name
)
225 # create the stack directory and files
226 utils
.create_dirs(dir)
227 utils
.create_dirs(compat_dir
)
228 PatchOrder
.create(dir)
229 config
.set(stackupgrade
.format_version_key(name
),
230 str(stackupgrade
.FORMAT_VERSION
))
232 return repository
.get_stack(name
)
235 def create(cls
, repository
, name
,
236 create_at
= None, parent_remote
= None, parent_branch
= None):
237 """Create and initialise a Git branch returning the L{Stack} object.
239 @param repository: The L{Repository} where the L{Stack} will be created
240 @param name: The name of the L{Stack}
241 @param create_at: The Git id used as the base for the newly created
243 @param parent_remote: The name of the remote Git branch
244 @param parent_branch: The name of the parent Git branch
246 git
.Branch
.create(repository
, name
, create_at
= create_at
)
247 stack
= cls
.initialise(repository
, name
)
248 stack
.set_parents(parent_remote
, parent_branch
)
251 class Repository(git
.Repository
):
252 """A git L{Repository<git.Repository>} with some added StGit-specific
254 def __init__(self
, *args
, **kwargs
):
255 git
.Repository
.__init__(self
, *args
, **kwargs
)
256 self
.__stacks
= {} # name -> Stack
258 def current_stack(self
):
259 return self
.get_stack()
260 def get_stack(self
, name
= None):
262 name
= self
.current_branch_name
263 if not name
in self
.__stacks
:
264 self
.__stacks
[name
] = Stack(self
, name
)
265 return self
.__stacks
[name
]