Commit | Line | Data |
---|---|---|
652b2e67 KH |
1 | """A Python class hierarchy wrapping the StGit on-disk metadata.""" |
2 | ||
cbe4567e KH |
3 | import os.path |
4 | from stgit import exception, utils | |
f5c820a8 | 5 | from stgit.lib import git, stackupgrade |
cbe4567e KH |
6 | |
7 | class Patch(object): | |
652b2e67 KH |
8 | """Represents an StGit patch. This class is mainly concerned with |
9 | reading and writing the on-disk representation of a patch.""" | |
cbe4567e KH |
10 | def __init__(self, stack, name): |
11 | self.__stack = stack | |
12 | self.__name = name | |
13 | name = property(lambda self: self.__name) | |
5a8e991e | 14 | @property |
cbe4567e KH |
15 | def __ref(self): |
16 | return 'refs/patches/%s/%s' % (self.__stack.name, self.__name) | |
17 | @property | |
5a8e991e KH |
18 | def __log_ref(self): |
19 | return self.__ref + '.log' | |
20 | @property | |
cbe4567e | 21 | def commit(self): |
5a8e991e KH |
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 = [] | |
f5f22afe | 39 | cd = git.CommitData(tree = new_commit.data.tree, parents = old_log, |
5a8e991e KH |
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) | |
cbe4567e | 68 | def set_commit(self, commit, msg): |
5a8e991e KH |
69 | self.__write_compat_files(commit, msg) |
70 | self.__stack.repository.refs.set(self.__ref, commit, msg) | |
cbe4567e | 71 | def delete(self): |
5a8e991e KH |
72 | self.__delete_compat_files() |
73 | self.__stack.repository.refs.delete(self.__ref) | |
cbe4567e KH |
74 | def is_applied(self): |
75 | return self.name in self.__stack.patchorder.applied | |
76 | def is_empty(self): | |
dcd32afa | 77 | return self.commit.data.is_nochange() |
cbe4567e KH |
78 | |
79 | class PatchOrder(object): | |
80 | """Keeps track of patch order, and which patches are applied. | |
81 | Works with patch names, not actual patches.""" | |
cbe4567e KH |
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)) | |
448e5d9d CM |
103 | hidden = property(lambda self: self.__get_list('hidden'), |
104 | lambda self, val: self.__set_list('hidden', val)) | |
d454cc06 CM |
105 | all = property(lambda self: self.applied + self.unapplied + self.hidden) |
106 | all_visible = property(lambda self: self.applied + self.unapplied) | |
cbe4567e KH |
107 | |
108 | class Patches(object): | |
652b2e67 KH |
109 | """Creates L{Patch} objects. Makes sure there is only one such object |
110 | per patch.""" | |
cbe4567e KH |
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 | ||
04c77bc5 | 133 | class Stack(git.Branch): |
652b2e67 KH |
134 | """Represents an StGit stack (that is, a git branch with some extra |
135 | metadata).""" | |
04c77bc5 CM |
136 | __repo_subdir = 'patches' |
137 | ||
cbe4567e | 138 | def __init__(self, repository, name): |
04c77bc5 | 139 | git.Branch.__init__(self, repository, name) |
cbe4567e KH |
140 | self.__patchorder = PatchOrder(self) |
141 | self.__patches = Patches(self) | |
317b386f KH |
142 | if not stackupgrade.update_to_current_format_version(repository, name): |
143 | raise exception.StgException('%s: branch not initialized' % name) | |
cbe4567e KH |
144 | patchorder = property(lambda self: self.__patchorder) |
145 | patches = property(lambda self: self.__patches) | |
146 | @property | |
147 | def directory(self): | |
04c77bc5 | 148 | return os.path.join(self.repository.directory, self.__repo_subdir, self.name) |
cbe4567e KH |
149 | @property |
150 | def base(self): | |
151 | if self.patchorder.applied: | |
152 | return self.patches.get(self.patchorder.applied[0] | |
153 | ).commit.data.parent | |
154 | else: | |
155 | return self.head | |
dcd32afa KH |
156 | def head_top_equal(self): |
157 | if not self.patchorder.applied: | |
158 | return True | |
159 | return self.head == self.patches.get(self.patchorder.applied[-1]).commit | |
cbe4567e KH |
160 | |
161 | class Repository(git.Repository): | |
652b2e67 KH |
162 | """A git L{Repository<git.Repository>} with some added StGit-specific |
163 | operations.""" | |
cbe4567e KH |
164 | def __init__(self, *args, **kwargs): |
165 | git.Repository.__init__(self, *args, **kwargs) | |
166 | self.__stacks = {} # name -> Stack | |
167 | @property | |
cbe4567e | 168 | def current_stack(self): |
13b26f1a CM |
169 | return self.get_stack() |
170 | def get_stack(self, name = None): | |
171 | if not name: | |
04c77bc5 | 172 | name = self.current_branch_name |
cbe4567e KH |
173 | if not name in self.__stacks: |
174 | self.__stacks[name] = Stack(self, name) | |
175 | return self.__stacks[name] |