Finished the option gui
[ugit.git] / ugitlibs / models.py
blobb0dcadd1e99f2fc1e9f312eca695c58749a647b9
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 for attr in ('add', 'add_or_remove', 'cat_file', 'checkout',
14 'create_branch', 'cherry_pick', 'commit', 'diff',
15 'diff_stat', 'format_patch', 'push', 'show','log',
16 'rebase', 'remote_url', 'rev_list_range'):
17 setattr(self, attr, getattr(git,attr))
19 # chdir to the root of the git tree. This is critical
20 # to being able to properly use the git porcelain.
21 cdup = git.show_cdup()
22 if cdup: os.chdir(cdup)
23 if not init: return
25 self.create(
26 #####################################################
27 # Used in various places
28 branch = git.current_branch(),
29 remotes = git.remote(),
30 remote = '',
31 local_branch = '',
32 remote_branch = '',
34 #####################################################
35 # Used primarily by the main UI
36 window_geom = utils.parse_geom(git.config('ugit.geometry')),
37 project = os.path.basename(os.getcwd()),
38 name = git.config('user.name'),
39 email = git.config('user.email'),
40 commitmsg = '',
41 staged = [],
42 unstaged = [],
43 untracked = [],
44 all_unstaged = [], # unstaged+untracked
46 #####################################################
47 # Used by the create branch dialog
48 revision = '',
49 local_branches = git.branch(remote=False),
50 remote_branches = git.branch(remote=True),
51 tags = git.tag(),
53 #####################################################
54 # Used by the commit/repo browser
55 directory = '',
56 revisions = [],
57 summaries = [],
59 # These are parallel lists
60 types = [],
61 sha1s = [],
62 names = [],
64 # All items below here are re-calculated in
65 # init_browser_data()
66 directories = [],
67 directory_entries = {},
69 # These are also parallel lists
70 subtree_types = [],
71 subtree_sha1s = [],
72 subtree_names = [],
75 def init_browser_data(self):
76 '''This scans over self.(names, sha1s, types) to generate
77 directories, directory_entries, and subtree_*'''
79 # Collect data for the model
80 if not self.get_branch(): return
82 self.subtree_types = []
83 self.subtree_sha1s = []
84 self.subtree_names = []
85 self.directories = []
86 self.directory_entries = {}
88 # Lookup the tree info
89 tree_info = git.ls_tree(self.get_branch())
91 self.set_types(map( lambda(x): x[1], tree_info ))
92 self.set_sha1s(map( lambda(x): x[2], tree_info ))
93 self.set_names(map( lambda(x): x[3], tree_info ))
95 if self.directory: self.directories.append('..')
97 dir_entries = self.directory_entries
98 dir_regex = re.compile('([^/]+)/')
99 dirs_seen = {}
100 subdirs_seen = {}
102 for idx, name in enumerate(self.names):
104 if not name.startswith(self.directory): continue
105 name = name[ len(self.directory): ]
107 if name.count('/'):
108 # This is a directory...
109 match = dir_regex.match(name)
110 if not match: continue
112 dirent = match.group(1) + '/'
113 if dirent not in self.directory_entries:
114 self.directory_entries[dirent] = []
116 if dirent not in dirs_seen:
117 dirs_seen[dirent] = True
118 self.directories.append(dirent)
120 entry = name.replace(dirent, '')
121 entry_match = dir_regex.match(entry)
122 if entry_match:
123 subdir = entry_match.group(1) + '/'
124 if subdir in subdirs_seen: continue
125 subdirs_seen[subdir] = True
126 dir_entries[dirent].append(subdir)
127 else:
128 dir_entries[dirent].append(entry)
129 else:
130 self.subtree_types.append(self.types[idx])
131 self.subtree_sha1s.append(self.sha1s[idx])
132 self.subtree_names.append(name)
134 def get_tree_node(self, idx):
135 return (self.get_types()[idx],
136 self.get_sha1s()[idx],
137 self.get_names()[idx] )
139 def get_subtree_node(self, idx):
140 return (self.get_subtree_types()[idx],
141 self.get_subtree_sha1s()[idx],
142 self.get_subtree_names()[idx] )
144 def get_all_branches(self):
145 return (self.get_local_branches() + self.get_remote_branches())
147 def set_remote(self,remote):
148 if not remote: return
149 self.set('remote',remote)
150 branches = utils.grep( '%s/\S+$' % remote, git.branch(remote=True))
151 self.set_remote_branches(branches)
153 def add_signoff(self,*rest):
154 '''Adds a standard Signed-off by: tag to the end
155 of the current commit message.'''
157 msg = self.get_commitmsg()
158 signoff =('Signed-off by: %s <%s>'
159 %(self.get_name(), self.get_email()))
161 if signoff not in msg:
162 self.set_commitmsg(msg + '\n\n' + signoff)
164 def apply_diff(self, filename):
165 return git.apply(filename)
167 def __get_squash_msg_path(self):
168 return os.path.join(os.getcwd(), '.git', 'SQUASH_MSG')
170 def has_squash_msg(self):
171 squash_msg = self.__get_squash_msg_path()
172 return os.path.exists(squash_msg)
174 def get_squash_msg(self):
175 return utils.slurp(self.__get_squash_msg_path())
177 def set_squash_msg(self):
178 self.model.set_commitmsg(self.model.get_squash_msg())
180 def get_prev_commitmsg(self,*rest):
181 '''Queries git for the latest commit message and sets it in
182 self.commitmsg.'''
183 commit_msg = []
184 commit_lines = git.show('HEAD').split('\n')
185 for idx, msg in enumerate(commit_lines):
186 if idx < 4: continue
187 msg = msg.lstrip()
188 if msg.startswith('diff --git'):
189 commit_msg.pop()
190 break
191 commit_msg.append(msg)
192 self.set_commitmsg(os.linesep.join(commit_msg).rstrip())
194 def update_status(self):
195 # This allows us to defer notification until the
196 # we finish processing data
197 notify_enabled = self.get_notify()
198 self.set_notify(False)
200 # Reset the staged and unstaged model lists
201 # NOTE: the model's unstaged list is used to
202 # hold both unstaged and untracked files.
203 self.staged = []
204 self.unstaged = []
205 self.untracked = []
207 # Read git status items
208 ( staged_items,
209 unstaged_items,
210 untracked_items ) = git.status()
212 # Gather items to be committed
213 for staged in staged_items:
214 if staged not in self.get_staged():
215 self.add_staged(staged)
217 # Gather unindexed items
218 for unstaged in unstaged_items:
219 if unstaged not in self.get_unstaged():
220 self.add_unstaged(unstaged)
222 # Gather untracked items
223 for untracked in untracked_items:
224 if untracked not in self.get_untracked():
225 self.add_untracked(untracked)
227 self.set_branch(git.current_branch())
228 self.set_all_unstaged(self.get_unstaged() + self.get_untracked())
229 self.set_remotes(git.remote())
230 self.set_remote_branches(git.branch(remote=True))
231 self.set_local_branches(git.branch(remote=False))
232 self.set_tags(git.tag())
233 self.set_revision('')
234 self.set_local_branch('')
235 self.set_remote_branch('')
237 # Re-enable notifications and emit changes
238 self.set_notify(notify_enabled)
239 self.notify_observers(
240 'branch', 'all_unstaged', 'staged',
241 'revision', 'remote', 'remotes',
242 'local_branches','remote_branches', 'tags')
244 def delete_branch(self, branch):
245 return git.branch(name=branch, delete=True)
247 def get_revision_sha1(self, idx):
248 return self.get_revisions()[idx]
250 def get_commit_diff(self, sha1):
251 commit = self.show(sha1)
252 first_newline = commit.index(os.linesep)
253 merge = commit[first_newline+1:].startswith('Merge:')
254 if merge:
255 return (commit + os.linesep*2
256 + self.diff(commit=sha1, cached=False,
257 suppress_header=False))
258 else:
259 return commit
261 def get_unstaged_item(self, idx):
262 return self.get_all_unstaged()[idx]
264 def get_diff_and_status(self, idx, staged=True):
265 if staged:
266 filename = self.get_staged()[idx]
267 if os.path.exists(filename):
268 status = 'Staged for commit'
269 else:
270 status = 'Staged for removal'
271 diff = self.diff(filename=filename, cached=True)
272 else:
273 filename = self.get_all_unstaged()[idx]
274 if os.path.isdir(filename):
275 status = 'Untracked directory'
276 diff = os.linesep.join(os.listdir(filename))
277 elif filename in self.get_unstaged():
278 status = 'Modified, not staged'
279 diff = self.diff(filename=filename, cached=False)
280 else:
281 status = 'Untracked, not staged'
283 file_type = utils.run_cmd('file','-b',filename)
284 if 'binary' in file_type or 'data' in file_type:
285 diff = utils.run_cmd('hexdump','-C',filename)
286 else:
287 if os.path.exists(filename):
288 file = open(filename, 'r')
289 diff = file.read()
290 file.close()
291 else:
292 diff = ''
293 return diff, status
295 def stage_changed(self):
296 git.add(self.get_unstaged())
297 self.update_status()
299 def stage_untracked(self):
300 git.add(self.get_untracked())
301 self.update_status()
303 def reset(self, items):
304 git.reset(items)
305 self.update_status()
307 def unstage_all(self):
308 git.reset(self.get_staged())
309 self.update_status()
311 def save_window_geom(self):
312 git.config('ugit.geometry', utils.get_geom())