1059955ae6c11e61dad9fa0e98c32f37c6988fcf
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
)
28 def __compat_dir(self
):
29 return os
.path
.join(self
.__stack
.directory
, 'patches', self
.__name
)
30 def __write_compat_files(self
, new_commit
, msg
):
31 """Write files used by the old infrastructure."""
32 def write(name
, val
, multiline
= False):
33 fn
= os
.path
.join(self
.__compat_dir
, name
)
35 utils
.write_string(fn
, val
, multiline
)
36 elif os
.path
.isfile(fn
):
40 old_log
= [self
.__stack
.repository
.refs
.get(self
.__log_ref
)]
43 cd
= git
.CommitData(tree
= new_commit
.data
.tree
, parents
= old_log
,
44 message
= '%s\t%s' %
(msg
, new_commit
.sha1
))
45 c
= self
.__stack
.repository
.commit(cd
)
46 self
.__stack
.repository
.refs
.set(self
.__log_ref
, c
, msg
)
49 write('authname', d
.author
.name
)
50 write('authemail', d
.author
.email
)
51 write('authdate', d
.author
.date
)
52 write('commname', d
.committer
.name
)
53 write('commemail', d
.committer
.email
)
54 write('description', d
.message
)
55 write('log', write_patchlog().sha1
)
56 write('top', new_commit
.sha1
)
57 write('bottom', d
.parent
.sha1
)
59 old_top_sha1
= self
.commit
.sha1
60 old_bottom_sha1
= self
.commit
.data
.parent
.sha1
63 old_bottom_sha1
= None
64 write('top.old', old_top_sha1
)
65 write('bottom.old', old_bottom_sha1
)
66 def __delete_compat_files(self
):
67 if os
.path
.isdir(self
.__compat_dir
):
68 for f
in os
.listdir(self
.__compat_dir
):
69 os
.remove(os
.path
.join(self
.__compat_dir
, f
))
70 os
.rmdir(self
.__compat_dir
)
72 # this compatibility log ref might not exist
73 self
.__stack
.repository
.refs
.delete(self
.__log_ref
)
76 def set_commit(self
, commit
, msg
):
77 self
.__write_compat_files(commit
, msg
)
78 self
.__stack
.repository
.refs
.set(self
.__ref
, commit
, msg
)
80 self
.__delete_compat_files()
81 self
.__stack
.repository
.refs
.delete(self
.__ref
)
83 return self
.name
in self
.__stack
.patchorder
.applied
85 return self
.commit
.data
.is_nochange()
87 class PatchOrder(object):
88 """Keeps track of patch order, and which patches are applied.
89 Works with patch names, not actual patches."""
90 def __init__(self
, stack
):
93 def __read_file(self
, fn
):
94 return tuple(utils
.read_strings(
95 os
.path
.join(self
.__stack
.directory
, fn
)))
96 def __write_file(self
, fn
, val
):
97 utils
.write_strings(os
.path
.join(self
.__stack
.directory
, fn
), val
)
98 def __get_list(self
, name
):
99 if not name
in self
.__lists
:
100 self
.__lists
[name
] = self
.__read_file(name
)
101 return self
.__lists
[name
]
102 def __set_list(self
, name
, val
):
104 if val
!= self
.__lists
.get(name
, None):
105 self
.__lists
[name
] = val
106 self
.__write_file(name
, val
)
107 applied
= property(lambda self
: self
.__get_list('applied'),
108 lambda self
, val
: self
.__set_list('applied', val
))
109 unapplied
= property(lambda self
: self
.__get_list('unapplied'),
110 lambda self
, val
: self
.__set_list('unapplied', val
))
111 hidden
= property(lambda self
: self
.__get_list('hidden'),
112 lambda self
, val
: self
.__set_list('hidden', val
))
113 all
= property(lambda self
: self
.applied
+ self
.unapplied
+ self
.hidden
)
114 all_visible
= property(lambda self
: self
.applied
+ self
.unapplied
)
117 def create(stackdir
):
118 """Create the PatchOrder specific files
120 utils
.create_empty_file(os
.path
.join(stackdir
, 'applied'))
121 utils
.create_empty_file(os
.path
.join(stackdir
, 'unapplied'))
122 utils
.create_empty_file(os
.path
.join(stackdir
, 'hidden'))
124 class Patches(object):
125 """Creates L{Patch} objects. Makes sure there is only one such object
127 def __init__(self
, stack
):
129 def create_patch(name
):
130 p
= Patch(self
.__stack
, name
)
131 p
.commit
# raise exception if the patch doesn't exist
133 self
.__patches
= git
.ObjectCache(create_patch
) # name -> Patch
134 def exists(self
, name
):
141 return self
.__patches
[name
]
142 def new(self
, name
, commit
, msg
):
143 assert not name
in self
.__patches
144 p
= Patch(self
.__stack
, name
)
145 p
.set_commit(commit
, msg
)
146 self
.__patches
[name
] = p
149 class Stack(git
.Branch
):
150 """Represents an StGit stack (that is, a git branch with some extra
152 __repo_subdir
= 'patches'
154 def __init__(self
, repository
, name
):
155 git
.Branch
.__init__(self
, repository
, name
)
156 self
.__patchorder
= PatchOrder(self
)
157 self
.__patches
= Patches(self
)
158 if not stackupgrade
.update_to_current_format_version(repository
, name
):
159 raise StackException('%s: branch not initialized' % name
)
160 patchorder
= property(lambda self
: self
.__patchorder
)
161 patches
= property(lambda self
: self
.__patches
)
164 return os
.path
.join(self
.repository
.directory
, self
.__repo_subdir
, self
.name
)
167 if self
.patchorder
.applied
:
168 return self
.patches
.get(self
.patchorder
.applied
[0]
172 def head_top_equal(self
):
173 if not self
.patchorder
.applied
:
175 return self
.head
== self
.patches
.get(self
.patchorder
.applied
[-1]).commit
177 def set_parents(self
, remote
, branch
):
179 self
.set_parent_remote(remote
)
181 self
.set_parent_branch(branch
)
184 def initialise(cls
, repository
, name
= None):
185 """Initialise a Git branch to handle patch series.
187 @param repository: The L{Repository} where the L{Stack} will be created
188 @param name: The name of the L{Stack}
191 name
= repository
.current_branch_name
192 # make sure that the corresponding Git branch exists
193 git
.Branch(repository
, name
)
195 dir = os
.path
.join(repository
.directory
, cls
.__repo_subdir
, name
)
196 compat_dir
= os
.path
.join(dir, 'patches')
197 if os
.path
.exists(dir):
198 raise StackException('%s: branch already initialized' % name
)
200 # create the stack directory and files
201 utils
.create_dirs(dir)
202 utils
.create_dirs(compat_dir
)
203 PatchOrder
.create(dir)
204 config
.set(stackupgrade
.format_version_key(name
),
205 str(stackupgrade
.FORMAT_VERSION
))
207 return repository
.get_stack(name
)
210 def create(cls
, repository
, name
,
211 create_at
= None, parent_remote
= None, parent_branch
= None):
212 """Create and initialise a Git branch returning the L{Stack} object.
214 @param repository: The L{Repository} where the L{Stack} will be created
215 @param name: The name of the L{Stack}
216 @param create_at: The Git id used as the base for the newly created
218 @param parent_remote: The name of the remote Git branch
219 @param parent_branch: The name of the parent Git branch
221 git
.Branch
.create(repository
, name
, create_at
= create_at
)
222 stack
= cls
.initialise(repository
, name
)
223 stack
.set_parents(parent_remote
, parent_branch
)
226 class Repository(git
.Repository
):
227 """A git L{Repository<git.Repository>} with some added StGit-specific
229 def __init__(self
, *args
, **kwargs
):
230 git
.Repository
.__init__(self
, *args
, **kwargs
)
231 self
.__stacks
= {} # name -> Stack
233 def current_stack(self
):
234 return self
.get_stack()
235 def get_stack(self
, name
= None):
237 name
= self
.current_branch_name
238 if not name
in self
.__stacks
:
239 self
.__stacks
[name
] = Stack(self
, name
)
240 return self
.__stacks
[name
]