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
)
71 self
.__stack
.repository
.refs
.delete(self
.__log_ref
)
72 def set_commit(self
, commit
, msg
):
73 self
.__write_compat_files(commit
, msg
)
74 self
.__stack
.repository
.refs
.set(self
.__ref
, commit
, msg
)
76 self
.__delete_compat_files()
77 self
.__stack
.repository
.refs
.delete(self
.__ref
)
79 return self
.name
in self
.__stack
.patchorder
.applied
81 return self
.commit
.data
.is_nochange()
83 class PatchOrder(object):
84 """Keeps track of patch order, and which patches are applied.
85 Works with patch names, not actual patches."""
86 def __init__(self
, stack
):
89 def __read_file(self
, fn
):
90 return tuple(utils
.read_strings(
91 os
.path
.join(self
.__stack
.directory
, fn
)))
92 def __write_file(self
, fn
, val
):
93 utils
.write_strings(os
.path
.join(self
.__stack
.directory
, fn
), val
)
94 def __get_list(self
, name
):
95 if not name
in self
.__lists
:
96 self
.__lists
[name
] = self
.__read_file(name
)
97 return self
.__lists
[name
]
98 def __set_list(self
, name
, val
):
100 if val
!= self
.__lists
.get(name
, None):
101 self
.__lists
[name
] = val
102 self
.__write_file(name
, val
)
103 applied
= property(lambda self
: self
.__get_list('applied'),
104 lambda self
, val
: self
.__set_list('applied', val
))
105 unapplied
= property(lambda self
: self
.__get_list('unapplied'),
106 lambda self
, val
: self
.__set_list('unapplied', val
))
107 hidden
= property(lambda self
: self
.__get_list('hidden'),
108 lambda self
, val
: self
.__set_list('hidden', val
))
109 all
= property(lambda self
: self
.applied
+ self
.unapplied
+ self
.hidden
)
110 all_visible
= property(lambda self
: self
.applied
+ self
.unapplied
)
113 def create(stackdir
):
114 """Create the PatchOrder specific files
116 utils
.create_empty_file(os
.path
.join(stackdir
, 'applied'))
117 utils
.create_empty_file(os
.path
.join(stackdir
, 'unapplied'))
118 utils
.create_empty_file(os
.path
.join(stackdir
, 'hidden'))
120 class Patches(object):
121 """Creates L{Patch} objects. Makes sure there is only one such object
123 def __init__(self
, stack
):
125 def create_patch(name
):
126 p
= Patch(self
.__stack
, name
)
127 p
.commit
# raise exception if the patch doesn't exist
129 self
.__patches
= git
.ObjectCache(create_patch
) # name -> Patch
130 def exists(self
, name
):
137 return self
.__patches
[name
]
138 def new(self
, name
, commit
, msg
):
139 assert not name
in self
.__patches
140 p
= Patch(self
.__stack
, name
)
141 p
.set_commit(commit
, msg
)
142 self
.__patches
[name
] = p
145 class Stack(git
.Branch
):
146 """Represents an StGit stack (that is, a git branch with some extra
148 __repo_subdir
= 'patches'
150 def __init__(self
, repository
, name
):
151 git
.Branch
.__init__(self
, repository
, name
)
152 self
.__patchorder
= PatchOrder(self
)
153 self
.__patches
= Patches(self
)
154 if not stackupgrade
.update_to_current_format_version(repository
, name
):
155 raise StackException('%s: branch not initialized' % name
)
156 patchorder
= property(lambda self
: self
.__patchorder
)
157 patches
= property(lambda self
: self
.__patches
)
160 return os
.path
.join(self
.repository
.directory
, self
.__repo_subdir
, self
.name
)
163 if self
.patchorder
.applied
:
164 return self
.patches
.get(self
.patchorder
.applied
[0]
168 def head_top_equal(self
):
169 if not self
.patchorder
.applied
:
171 return self
.head
== self
.patches
.get(self
.patchorder
.applied
[-1]).commit
173 def set_parents(self
, remote
, branch
):
175 self
.set_parent_remote(remote
)
177 self
.set_parent_branch(branch
)
180 def initialise(cls
, repository
, name
= None):
181 """Initialise a Git branch to handle patch series.
183 @param repository: The L{Repository} where the L{Stack} will be created
184 @param name: The name of the L{Stack}
187 name
= repository
.current_branch_name
188 # make sure that the corresponding Git branch exists
189 git
.Branch(repository
, name
)
191 dir = os
.path
.join(repository
.directory
, cls
.__repo_subdir
, name
)
192 compat_dir
= os
.path
.join(dir, 'patches')
193 if os
.path
.exists(dir):
194 raise StackException('%s: branch already initialized' % name
)
196 # create the stack directory and files
197 utils
.create_dirs(dir)
198 utils
.create_dirs(compat_dir
)
199 PatchOrder
.create(dir)
200 config
.set(stackupgrade
.format_version_key(name
),
201 str(stackupgrade
.FORMAT_VERSION
))
203 return repository
.get_stack(name
)
206 def create(cls
, repository
, name
,
207 create_at
= None, parent_remote
= None, parent_branch
= None):
208 """Create and initialise a Git branch returning the L{Stack} object.
210 @param repository: The L{Repository} where the L{Stack} will be created
211 @param name: The name of the L{Stack}
212 @param create_at: The Git id used as the base for the newly created
214 @param parent_remote: The name of the remote Git branch
215 @param parent_branch: The name of the parent Git branch
217 git
.Branch
.create(repository
, name
, create_at
= create_at
)
218 stack
= cls
.initialise(repository
, name
)
219 stack
.set_parents(parent_remote
, parent_branch
)
222 class Repository(git
.Repository
):
223 """A git L{Repository<git.Repository>} with some added StGit-specific
225 def __init__(self
, *args
, **kwargs
):
226 git
.Repository
.__init__(self
, *args
, **kwargs
)
227 self
.__stacks
= {} # name -> Stack
229 def current_stack(self
):
230 return self
.get_stack()
231 def get_stack(self
, name
= None):
233 name
= self
.current_branch_name
234 if not name
in self
.__stacks
:
235 self
.__stacks
[name
] = Stack(self
, name
)
236 return self
.__stacks
[name
]