Rewrite the "series" command for the new infrastructure
[stgit] / stgit / lib / stack.py
1 import os.path
2 from stgit import exception, utils
3 from stgit.lib import git, stackupgrade
4
5 class Patch(object):
6 def __init__(self, stack, name):
7 self.__stack = stack
8 self.__name = name
9 name = property(lambda self: self.__name)
10 @property
11 def __ref(self):
12 return 'refs/patches/%s/%s' % (self.__stack.name, self.__name)
13 @property
14 def __log_ref(self):
15 return self.__ref + '.log'
16 @property
17 def commit(self):
18 return self.__stack.repository.refs.get(self.__ref)
19 @property
20 def __compat_dir(self):
21 return os.path.join(self.__stack.directory, 'patches', self.__name)
22 def __write_compat_files(self, new_commit, msg):
23 """Write files used by the old infrastructure."""
24 def write(name, val, multiline = False):
25 fn = os.path.join(self.__compat_dir, name)
26 if val:
27 utils.write_string(fn, val, multiline)
28 elif os.path.isfile(fn):
29 os.remove(fn)
30 def write_patchlog():
31 try:
32 old_log = [self.__stack.repository.refs.get(self.__log_ref)]
33 except KeyError:
34 old_log = []
35 cd = git.CommitData(tree = new_commit.data.tree, parents = old_log,
36 message = '%s\t%s' % (msg, new_commit.sha1))
37 c = self.__stack.repository.commit(cd)
38 self.__stack.repository.refs.set(self.__log_ref, c, msg)
39 return c
40 d = new_commit.data
41 write('authname', d.author.name)
42 write('authemail', d.author.email)
43 write('authdate', d.author.date)
44 write('commname', d.committer.name)
45 write('commemail', d.committer.email)
46 write('description', d.message)
47 write('log', write_patchlog().sha1)
48 write('top', new_commit.sha1)
49 write('bottom', d.parent.sha1)
50 try:
51 old_top_sha1 = self.commit.sha1
52 old_bottom_sha1 = self.commit.data.parent.sha1
53 except KeyError:
54 old_top_sha1 = None
55 old_bottom_sha1 = None
56 write('top.old', old_top_sha1)
57 write('bottom.old', old_bottom_sha1)
58 def __delete_compat_files(self):
59 if os.path.isdir(self.__compat_dir):
60 for f in os.listdir(self.__compat_dir):
61 os.remove(os.path.join(self.__compat_dir, f))
62 os.rmdir(self.__compat_dir)
63 self.__stack.repository.refs.delete(self.__log_ref)
64 def set_commit(self, commit, msg):
65 self.__write_compat_files(commit, msg)
66 self.__stack.repository.refs.set(self.__ref, commit, msg)
67 def delete(self):
68 self.__delete_compat_files()
69 self.__stack.repository.refs.delete(self.__ref)
70 def is_applied(self):
71 return self.name in self.__stack.patchorder.applied
72 def is_empty(self):
73 return self.commit.data.is_nochange()
74
75 class PatchOrder(object):
76 """Keeps track of patch order, and which patches are applied.
77 Works with patch names, not actual patches."""
78 def __init__(self, stack):
79 self.__stack = stack
80 self.__lists = {}
81 def __read_file(self, fn):
82 return tuple(utils.read_strings(
83 os.path.join(self.__stack.directory, fn)))
84 def __write_file(self, fn, val):
85 utils.write_strings(os.path.join(self.__stack.directory, fn), val)
86 def __get_list(self, name):
87 if not name in self.__lists:
88 self.__lists[name] = self.__read_file(name)
89 return self.__lists[name]
90 def __set_list(self, name, val):
91 val = tuple(val)
92 if val != self.__lists.get(name, None):
93 self.__lists[name] = val
94 self.__write_file(name, val)
95 applied = property(lambda self: self.__get_list('applied'),
96 lambda self, val: self.__set_list('applied', val))
97 unapplied = property(lambda self: self.__get_list('unapplied'),
98 lambda self, val: self.__set_list('unapplied', val))
99 hidden = property(lambda self: self.__get_list('hidden'),
100 lambda self, val: self.__set_list('hidden', val))
101 # don't return the hidden patches, these have to be returned explicitly
102 all = property(lambda self: self.applied + self.unapplied)
103
104 class Patches(object):
105 """Creates Patch objects."""
106 def __init__(self, stack):
107 self.__stack = stack
108 def create_patch(name):
109 p = Patch(self.__stack, name)
110 p.commit # raise exception if the patch doesn't exist
111 return p
112 self.__patches = git.ObjectCache(create_patch) # name -> Patch
113 def exists(self, name):
114 try:
115 self.get(name)
116 return True
117 except KeyError:
118 return False
119 def get(self, name):
120 return self.__patches[name]
121 def new(self, name, commit, msg):
122 assert not name in self.__patches
123 p = Patch(self.__stack, name)
124 p.set_commit(commit, msg)
125 self.__patches[name] = p
126 return p
127
128 class Stack(object):
129 def __init__(self, repository, name):
130 self.__repository = repository
131 self.__name = name
132 try:
133 self.head
134 except KeyError:
135 raise exception.StgException('%s: no such branch' % name)
136 self.__patchorder = PatchOrder(self)
137 self.__patches = Patches(self)
138 if not stackupgrade.update_to_current_format_version(repository, name):
139 raise exception.StgException('%s: branch not initialized' % name)
140 name = property(lambda self: self.__name)
141 repository = property(lambda self: self.__repository)
142 patchorder = property(lambda self: self.__patchorder)
143 patches = property(lambda self: self.__patches)
144 @property
145 def directory(self):
146 return os.path.join(self.__repository.directory, 'patches', self.__name)
147 def __ref(self):
148 return 'refs/heads/%s' % self.__name
149 @property
150 def head(self):
151 return self.__repository.refs.get(self.__ref())
152 def set_head(self, commit, msg):
153 self.__repository.refs.set(self.__ref(), commit, msg)
154 @property
155 def base(self):
156 if self.patchorder.applied:
157 return self.patches.get(self.patchorder.applied[0]
158 ).commit.data.parent
159 else:
160 return self.head
161 def head_top_equal(self):
162 if not self.patchorder.applied:
163 return True
164 return self.head == self.patches.get(self.patchorder.applied[-1]).commit
165
166 class Repository(git.Repository):
167 def __init__(self, *args, **kwargs):
168 git.Repository.__init__(self, *args, **kwargs)
169 self.__stacks = {} # name -> Stack
170 @property
171 def current_branch(self):
172 return utils.strip_leading('refs/heads/', self.head)
173 @property
174 def current_stack(self):
175 return self.get_stack(self.current_branch)
176 def get_stack(self, name):
177 if not name in self.__stacks:
178 self.__stacks[name] = Stack(self, name)
179 return self.__stacks[name]