Remove some unused data and small fixups
[ugit.git] / ugitlibs / models.py
blob3da00e34f5fee38612248115144c5bad2e423f1a
1 import os
2 import re
4 import git
5 import utils
6 import model
8 class Model(model.Model):
9 def __init__(self, init=True):
10 model.Model.__init__(self)
12 # These methods are best left implemented in git.py
13 git_attrs=(
14 'add',
15 'add_or_remove',
16 'cat_file',
17 'checkout',
18 'create_branch',
19 'cherry_pick',
20 'commit',
21 'diff',
22 'diff_stat',
23 'format_patch',
24 'push',
25 'show',
26 'log',
27 'rebase',
28 'remote_url',
29 'rev_list_range',
32 for attr in git_attrs:
33 setattr(self, attr, getattr(git,attr))
35 # chdir to the root of the git tree. This is critical
36 # to being able to properly use the git porcelain.
37 cdup = git.show_cdup()
38 if cdup: os.chdir(cdup)
40 self.__config_types = {
41 'merge.verbosity':'int',
42 'gui.diffcontext':'int',
43 'gui.pruneduringfetch':'bool',
44 'merge.summary':'bool',
47 if not init: return
49 self.read_configs()
50 self.create(
51 #####################################################
52 # Used in various places
53 branch = git.current_branch(),
54 remotes = git.remote(),
55 remote = '',
56 local_branch = '',
57 remote_branch = '',
59 #####################################################
60 # Used primarily by the main UI
61 window_geom = utils.parse_geom(git.config('ugit.geometry')),
62 project = os.path.basename(os.getcwd()),
63 commitmsg = '',
64 staged = [],
65 unstaged = [],
66 untracked = [],
67 all_unstaged = [], # unstaged+untracked
69 #####################################################
70 # Used by the create branch dialog
71 revision = '',
72 local_branches = git.branch(remote=False),
73 remote_branches = git.branch(remote=True),
74 tags = git.tag(),
76 #####################################################
77 # Used by the commit/repo browser
78 directory = '',
79 revisions = [],
80 summaries = [],
82 # These are parallel lists
83 types = [],
84 sha1s = [],
85 names = [],
87 # All items below here are re-calculated in
88 # init_browser_data()
89 directories = [],
90 directory_entries = {},
92 # These are also parallel lists
93 subtree_types = [],
94 subtree_sha1s = [],
95 subtree_names = [],
98 def get_config(self,key,local=True):
99 if local:
100 config = self.get_local_config()
101 else:
102 config = self.get_global_config()
103 try:
104 return config[key]
105 except:
106 return None
108 def read_configs(self):
109 def config_to_dict(config):
110 newdict = {}
111 for line in config.splitlines():
112 k, v = line.split('=')
113 try:
114 linetype = self.__config_types[k]
115 if linetype == 'int':
116 v = int(v)
117 elif linetype == 'bool':
118 v = bool(eval(v[0].upper()+v[1:]))
119 except: pass
120 newdict[k]=v
121 return newdict
123 local_config = git.git('config','--global','--list')
124 global_config = git.git('config','--list')
126 self.set('local_config', config_to_dict(local_config))
127 self.set('global_config', config_to_dict(global_config))
129 def init_browser_data(self):
130 '''This scans over self.(names, sha1s, types) to generate
131 directories, directory_entries, and subtree_*'''
133 # Collect data for the model
134 if not self.get_branch(): return
136 self.subtree_types = []
137 self.subtree_sha1s = []
138 self.subtree_names = []
139 self.directories = []
140 self.directory_entries = {}
142 # Lookup the tree info
143 tree_info = git.ls_tree(self.get_branch())
145 self.set_types(map( lambda(x): x[1], tree_info ))
146 self.set_sha1s(map( lambda(x): x[2], tree_info ))
147 self.set_names(map( lambda(x): x[3], tree_info ))
149 if self.directory: self.directories.append('..')
151 dir_entries = self.directory_entries
152 dir_regex = re.compile('([^/]+)/')
153 dirs_seen = {}
154 subdirs_seen = {}
156 for idx, name in enumerate(self.names):
158 if not name.startswith(self.directory): continue
159 name = name[ len(self.directory): ]
161 if name.count('/'):
162 # This is a directory...
163 match = dir_regex.match(name)
164 if not match: continue
166 dirent = match.group(1) + '/'
167 if dirent not in self.directory_entries:
168 self.directory_entries[dirent] = []
170 if dirent not in dirs_seen:
171 dirs_seen[dirent] = True
172 self.directories.append(dirent)
174 entry = name.replace(dirent, '')
175 entry_match = dir_regex.match(entry)
176 if entry_match:
177 subdir = entry_match.group(1) + '/'
178 if subdir in subdirs_seen: continue
179 subdirs_seen[subdir] = True
180 dir_entries[dirent].append(subdir)
181 else:
182 dir_entries[dirent].append(entry)
183 else:
184 self.subtree_types.append(self.types[idx])
185 self.subtree_sha1s.append(self.sha1s[idx])
186 self.subtree_names.append(name)
188 def get_tree_node(self, idx):
189 return (self.get_types()[idx],
190 self.get_sha1s()[idx],
191 self.get_names()[idx] )
193 def get_subtree_node(self, idx):
194 return (self.get_subtree_types()[idx],
195 self.get_subtree_sha1s()[idx],
196 self.get_subtree_names()[idx] )
198 def get_all_branches(self):
199 return (self.get_local_branches() + self.get_remote_branches())
201 def set_remote(self,remote):
202 if not remote: return
203 self.set('remote',remote)
204 branches = utils.grep( '%s/\S+$' % remote, git.branch(remote=True))
205 self.set_remote_branches(branches)
207 def add_signoff(self,*rest):
208 '''Adds a standard Signed-off by: tag to the end
209 of the current commit message.'''
211 msg = self.get_commitmsg()
212 signoff =('Signed-off by: %s <%s>' % (
213 self.get_config('user.name'),
214 self.get_config('user.email')))
216 if signoff not in msg:
217 self.set_commitmsg(msg + os.linesep*2 + signoff)
219 def apply_diff(self, filename):
220 return git.apply(filename)
222 def __get_squash_msg_path(self):
223 return os.path.join(os.getcwd(), '.git', 'SQUASH_MSG')
225 def has_squash_msg(self):
226 squash_msg = self.__get_squash_msg_path()
227 return os.path.exists(squash_msg)
229 def get_squash_msg(self):
230 return utils.slurp(self.__get_squash_msg_path())
232 def set_squash_msg(self):
233 self.model.set_commitmsg(self.model.get_squash_msg())
235 def get_prev_commitmsg(self,*rest):
236 '''Queries git for the latest commit message and sets it in
237 self.commitmsg.'''
238 commit_msg = []
239 commit_lines = git.show('HEAD').split('\n')
240 for idx, msg in enumerate(commit_lines):
241 if idx < 4: continue
242 msg = msg.lstrip()
243 if msg.startswith('diff --git'):
244 commit_msg.pop()
245 break
246 commit_msg.append(msg)
247 self.set_commitmsg(os.linesep.join(commit_msg).rstrip())
249 def update_status(self):
250 # This allows us to defer notification until the
251 # we finish processing data
252 notify_enabled = self.get_notify()
253 self.set_notify(False)
255 # Reset the staged and unstaged model lists
256 # NOTE: the model's unstaged list is used to
257 # hold both unstaged and untracked files.
258 self.staged = []
259 self.unstaged = []
260 self.untracked = []
262 # Read git status items
263 ( staged_items,
264 unstaged_items,
265 untracked_items ) = git.status()
267 # Gather items to be committed
268 for staged in staged_items:
269 if staged not in self.get_staged():
270 self.add_staged(staged)
272 # Gather unindexed items
273 for unstaged in unstaged_items:
274 if unstaged not in self.get_unstaged():
275 self.add_unstaged(unstaged)
277 # Gather untracked items
278 for untracked in untracked_items:
279 if untracked not in self.get_untracked():
280 self.add_untracked(untracked)
282 self.set_branch(git.current_branch())
283 self.set_all_unstaged(self.get_unstaged() + self.get_untracked())
284 self.set_remotes(git.remote())
285 self.set_remote_branches(git.branch(remote=True))
286 self.set_local_branches(git.branch(remote=False))
287 self.set_tags(git.tag())
288 self.set_revision('')
289 self.set_local_branch('')
290 self.set_remote_branch('')
292 self.read_configs()
294 # Re-enable notifications and emit changes
295 self.set_notify(notify_enabled)
296 self.notify_observers(
297 'branch', 'all_unstaged', 'staged',
298 'revision', 'remote', 'remotes',
299 'local_branches','remote_branches', 'tags')
301 def delete_branch(self, branch):
302 return git.branch(name=branch, delete=True)
304 def get_revision_sha1(self, idx):
305 return self.get_revisions()[idx]
307 def get_commit_diff(self, sha1):
308 commit = self.show(sha1)
309 first_newline = commit.index(os.linesep)
310 merge = commit[first_newline+1:].startswith('Merge:')
311 if merge:
312 return (commit + os.linesep*2
313 + self.diff(commit=sha1, cached=False,
314 suppress_header=False))
315 else:
316 return commit
318 def get_unstaged_item(self, idx):
319 return self.get_all_unstaged()[idx]
321 def get_diff_and_status(self, idx, staged=True):
322 if staged:
323 filename = self.get_staged()[idx]
324 if os.path.exists(filename):
325 status = 'Staged for commit'
326 else:
327 status = 'Staged for removal'
328 diff = self.diff(filename=filename, cached=True)
329 else:
330 filename = self.get_all_unstaged()[idx]
331 if os.path.isdir(filename):
332 status = 'Untracked directory'
333 diff = os.linesep.join(os.listdir(filename))
334 elif filename in self.get_unstaged():
335 status = 'Modified, not staged'
336 diff = self.diff(filename=filename, cached=False)
337 else:
338 status = 'Untracked, not staged'
340 file_type = utils.run_cmd('file','-b',filename)
341 if 'binary' in file_type or 'data' in file_type:
342 diff = utils.run_cmd('hexdump','-C',filename)
343 else:
344 if os.path.exists(filename):
345 file = open(filename, 'r')
346 diff = file.read()
347 file.close()
348 else:
349 diff = ''
350 return diff, status
352 def stage_changed(self):
353 git.add(self.get_unstaged())
354 self.update_status()
356 def stage_untracked(self):
357 git.add(self.get_untracked())
358 self.update_status()
360 def reset(self, items):
361 git.reset(items)
362 self.update_status()
364 def unstage_all(self):
365 git.reset(self.get_staged())
366 self.update_status()
368 def save_window_geom(self):
369 git.config('ugit.geometry', utils.get_geom())