chiark / gitweb /
6a2b40d6d7d15bf38666854f76239a0c97666f52
[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()
176     def get_stack(self, name = None):
177         if not name:
178             name = self.current_branch
179         if not name in self.__stacks:
180             self.__stacks[name] = Stack(self, name)
181         return self.__stacks[name]