Refactor methods to allow more delegation to the model and view
[ugit.git] / ugitlibs / models.py
blobc16349241021711c9828f680a66eb498cd44ae86
1 import os
2 import re
4 import git
5 import utils
6 import model
8 class Model(model.Model):
9 def __init__(self):
10 model.Model.__init__(self)
12 # chdir to the root of the git tree. This is critical
13 # to being able to properly use the git porcelain.
14 cdup = git.show_cdup()
15 if cdup: os.chdir(cdup)
17 self.create(
18 #####################################################
19 # Used in various places
20 branch = git.current_branch(),
21 remotes = git.remote(),
22 remote = '',
23 local_branch = '',
24 remote_branch = '',
26 #####################################################
27 # Used primarily by the main UI
28 window_geom = utils.parse_geom(git.config('ugit.geometry')),
29 project = os.path.basename(os.getcwd()),
30 name = git.config('user.name'),
31 email = git.config('user.email'),
32 commitmsg = '',
33 staged = [],
34 unstaged = [],
35 untracked = [],
36 all_unstaged = [], # unstaged+untracked
38 #####################################################
39 # Used by the create branch dialog
40 revision = '',
41 local_branches = git.branch(remote=False),
42 remote_branches = git.branch(remote=True),
43 tags = git.tag(),
45 #####################################################
46 # Used by the repo browser
47 directory = '',
49 # These are parallel lists
50 types = [],
51 sha1s = [],
52 names = [],
54 # All items below here are re-calculated in
55 # init_browser_data()
56 directories = [],
57 directory_entries = {},
59 # These are also parallel lists
60 subtree_types = [],
61 subtree_sha1s = [],
62 subtree_names = [],
65 # These methods are best left implemented in git.py
66 for attr in ('add', 'add_or_remove', 'cat_file', 'checkout',
67 'create_branch', 'cherry_pick', 'commit', 'diff',
68 'diff_stat', 'format_patch', 'push', 'show','log',
69 'rebase', 'remote_url', 'rev_list_range'):
70 setattr(self, attr, getattr(git,attr))
72 def init_browser_data(self):
73 '''This scans over self.(names, sha1s, types) to generate
74 directories, directory_entries, and subtree_*'''
76 # Collect data for the model
77 if not self.get_branch(): return
79 self.subtree_types = []
80 self.subtree_sha1s = []
81 self.subtree_names = []
82 self.directories = []
83 self.directory_entries = {}
85 # Lookup the tree info
86 tree_info = git.ls_tree(self.get_branch())
88 self.set_types(map( lambda(x): x[1], tree_info ))
89 self.set_sha1s(map( lambda(x): x[2], tree_info ))
90 self.set_names(map( lambda(x): x[3], tree_info ))
92 if self.directory: self.directories.append('..')
94 dir_entries = self.directory_entries
95 dir_regex = re.compile('([^/]+)/')
96 dirs_seen = {}
97 subdirs_seen = {}
99 for idx, name in enumerate(self.names):
101 if not name.startswith(self.directory): continue
102 name = name[ len(self.directory): ]
104 if name.count('/'):
105 # This is a directory...
106 match = dir_regex.match(name)
107 if not match: continue
109 dirent = match.group(1) + '/'
110 if dirent not in self.directory_entries:
111 self.directory_entries[dirent] = []
113 if dirent not in dirs_seen:
114 dirs_seen[dirent] = True
115 self.directories.append(dirent)
117 entry = name.replace(dirent, '')
118 entry_match = dir_regex.match(entry)
119 if entry_match:
120 subdir = entry_match.group(1) + '/'
121 if subdir in subdirs_seen: continue
122 subdirs_seen[subdir] = True
123 dir_entries[dirent].append(subdir)
124 else:
125 dir_entries[dirent].append(entry)
126 else:
127 self.subtree_types.append(self.types[idx])
128 self.subtree_sha1s.append(self.sha1s[idx])
129 self.subtree_names.append(name)
131 def get_tree_node(self, idx):
132 return (self.get_types()[idx],
133 self.get_sha1s()[idx],
134 self.get_names()[idx] )
136 def get_subtree_node(self, idx):
137 return (self.get_subtree_types()[idx],
138 self.get_subtree_sha1s()[idx],
139 self.get_subtree_names()[idx] )
141 def get_all_branches(self):
142 return (self.get_local_branches() + self.get_remote_branches())
144 def set_remote(self,remote):
145 if not remote: return
146 self.set('remote',remote)
147 branches = utils.grep( '%s/\S+$' % remote, git.branch(remote=True))
148 self.set_remote_branches(branches)
150 def add_signoff(self,*rest):
151 '''Adds a standard Signed-off by: tag to the end
152 of the current commit message.'''
154 msg = self.get_commitmsg()
155 signoff =('Signed-off by: %s <%s>'
156 %(self.get_name(), self.get_email()))
158 if signoff not in msg:
159 self.set_commitmsg(msg + '\n\n' + signoff)
161 def apply_diff(self, filename):
162 return git.apply(filename)
164 def __get_squash_msg_path(self):
165 return os.path.join(os.getcwd(), '.git', 'SQUASH_MSG')
167 def has_squash_msg(self):
168 squash_msg = self.__get_squash_msg_path()
169 return os.path.exists(squash_msg)
171 def get_squash_msg(self):
172 return utils.slurp(self.__get_squash_msg_path())
174 def set_squash_msg(self):
175 self.model.set_commitmsg(self.model.get_squash_msg())
177 def get_prev_commitmsg(self,*rest):
178 '''Queries git for the latest commit message and sets it in
179 self.commitmsg.'''
180 commit_msg = []
181 commit_lines = git.show('HEAD').split('\n')
182 for idx, msg in enumerate(commit_lines):
183 if idx < 4: continue
184 msg = msg.lstrip()
185 if msg.startswith('diff --git'):
186 commit_msg.pop()
187 break
188 commit_msg.append(msg)
189 self.set_commitmsg('\n'.join(commit_msg).rstrip())
191 def update_status(self):
192 # This allows us to defer notification until the
193 # we finish processing data
194 notify_enabled = self.get_notify()
195 self.set_notify(False)
197 # Reset the staged and unstaged model lists
198 # NOTE: the model's unstaged list is used to
199 # hold both unstaged and untracked files.
200 self.staged = []
201 self.unstaged = []
202 self.untracked = []
204 # Read git status items
205 ( staged_items,
206 unstaged_items,
207 untracked_items ) = git.status()
209 # Gather items to be committed
210 for staged in staged_items:
211 if staged not in self.get_staged():
212 self.add_staged(staged)
214 # Gather unindexed items
215 for unstaged in unstaged_items:
216 if unstaged not in self.get_unstaged():
217 self.add_unstaged(unstaged)
219 # Gather untracked items
220 for untracked in untracked_items:
221 if untracked not in self.get_untracked():
222 self.add_untracked(untracked)
224 self.set_branch(git.current_branch())
225 self.set_all_unstaged(self.get_unstaged() + self.get_untracked())
226 self.set_remotes(git.remote())
227 self.set_remote_branches(git.branch(remote=True))
228 self.set_local_branches(git.branch(remote=False))
229 self.set_tags(git.tag())
230 self.set_revision('')
231 self.set_local_branch('')
232 self.set_remote_branch('')
234 # Re-enable notifications and emit changes
235 self.set_notify(notify_enabled)
236 self.notify_observers(
237 'branch', 'all_unstaged', 'staged',
238 'revision', 'remote', 'remotes',
239 'local_branches','remote_branches', 'tags')
241 def delete_branch(self, branch):
242 return git.branch(name=branch, delete=True)
244 def get_unstaged_item(self, idx):
245 return self.get_all_unstaged()[idx]
247 def get_diff_and_status(self, idx, staged=True):
248 if staged:
249 filename = self.get_staged()[idx]
250 if os.path.exists(filename):
251 status = 'Staged for commit'
252 else:
253 status = 'Staged for removal'
254 diff = self.diff(filename=filename, cached=True)
255 else:
256 filename = self.get_all_unstaged()[idx]
257 if os.path.isdir(filename):
258 status = 'Untracked directory'
259 diff = os.linesep.join(os.listdir(filename))
260 elif filename in self.get_unstaged():
261 status = 'Modified, not staged'
262 diff = self.diff(filename=filename, cached=False)
263 else:
264 status = 'Untracked, not staged'
266 file_type = git.run_cmd('file','-b',filename)
267 if 'binary' in file_type or 'data' in file_type:
268 diff = git.run_cmd('hexdump','-C',filename)
269 else:
270 if os.path.exists(filename):
271 file = open(filename, 'r')
272 diff = file.read()
273 file.close()
274 else:
275 diff = ''
276 return diff, status
278 def stage_changed(self):
279 git.add(self.get_unstaged())
280 self.update_status()
282 def stage_untracked(self):
283 git.add(self.get_untracked())
284 self.update_status()
286 def reset(self, items):
287 git.reset(items)
288 self.update_status()
290 def unstage_all(self):
291 git.reset(self.get_staged())
292 self.update_status()
294 def save_window_geom(self):
295 git.config('ugit.geometry', utils.get_geom())