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