Start separating global and local config settings
[ugit.git] / ugitlibs / models.py
blob07297615ec12d5401d46627f40203fdddf1b8801
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 local_name = git.config('user.name'),
39 local_email = git.config('user.email'),
40 global_name = git.config('user.name', local=False),
41 global_email = git.config('user.email', local=False),
42 commitmsg = '',
43 staged = [],
44 unstaged = [],
45 untracked = [],
46 all_unstaged = [], # unstaged+untracked
48 #####################################################
49 # Used by the create branch dialog
50 revision = '',
51 local_branches = git.branch(remote=False),
52 remote_branches = git.branch(remote=True),
53 tags = git.tag(),
55 #####################################################
56 # Used by the commit/repo browser
57 directory = '',
58 revisions = [],
59 summaries = [],
61 # These are parallel lists
62 types = [],
63 sha1s = [],
64 names = [],
66 # All items below here are re-calculated in
67 # init_browser_data()
68 directories = [],
69 directory_entries = {},
71 # These are also parallel lists
72 subtree_types = [],
73 subtree_sha1s = [],
74 subtree_names = [],
77 def init_browser_data(self):
78 '''This scans over self.(names, sha1s, types) to generate
79 directories, directory_entries, and subtree_*'''
81 # Collect data for the model
82 if not self.get_branch(): return
84 self.subtree_types = []
85 self.subtree_sha1s = []
86 self.subtree_names = []
87 self.directories = []
88 self.directory_entries = {}
90 # Lookup the tree info
91 tree_info = git.ls_tree(self.get_branch())
93 self.set_types(map( lambda(x): x[1], tree_info ))
94 self.set_sha1s(map( lambda(x): x[2], tree_info ))
95 self.set_names(map( lambda(x): x[3], tree_info ))
97 if self.directory: self.directories.append('..')
99 dir_entries = self.directory_entries
100 dir_regex = re.compile('([^/]+)/')
101 dirs_seen = {}
102 subdirs_seen = {}
104 for idx, name in enumerate(self.names):
106 if not name.startswith(self.directory): continue
107 name = name[ len(self.directory): ]
109 if name.count('/'):
110 # This is a directory...
111 match = dir_regex.match(name)
112 if not match: continue
114 dirent = match.group(1) + '/'
115 if dirent not in self.directory_entries:
116 self.directory_entries[dirent] = []
118 if dirent not in dirs_seen:
119 dirs_seen[dirent] = True
120 self.directories.append(dirent)
122 entry = name.replace(dirent, '')
123 entry_match = dir_regex.match(entry)
124 if entry_match:
125 subdir = entry_match.group(1) + '/'
126 if subdir in subdirs_seen: continue
127 subdirs_seen[subdir] = True
128 dir_entries[dirent].append(subdir)
129 else:
130 dir_entries[dirent].append(entry)
131 else:
132 self.subtree_types.append(self.types[idx])
133 self.subtree_sha1s.append(self.sha1s[idx])
134 self.subtree_names.append(name)
136 def get_tree_node(self, idx):
137 return (self.get_types()[idx],
138 self.get_sha1s()[idx],
139 self.get_names()[idx] )
141 def get_subtree_node(self, idx):
142 return (self.get_subtree_types()[idx],
143 self.get_subtree_sha1s()[idx],
144 self.get_subtree_names()[idx] )
146 def get_all_branches(self):
147 return (self.get_local_branches() + self.get_remote_branches())
149 def set_remote(self,remote):
150 if not remote: return
151 self.set('remote',remote)
152 branches = utils.grep( '%s/\S+$' % remote, git.branch(remote=True))
153 self.set_remote_branches(branches)
155 def add_signoff(self,*rest):
156 '''Adds a standard Signed-off by: tag to the end
157 of the current commit message.'''
159 msg = self.get_commitmsg()
160 signoff =('Signed-off by: %s <%s>'
161 % (self.get_local_name(), self.get_local_email()))
163 if signoff not in msg:
164 self.set_commitmsg(msg + os.linesep*2 + signoff)
166 def apply_diff(self, filename):
167 return git.apply(filename)
169 def __get_squash_msg_path(self):
170 return os.path.join(os.getcwd(), '.git', 'SQUASH_MSG')
172 def has_squash_msg(self):
173 squash_msg = self.__get_squash_msg_path()
174 return os.path.exists(squash_msg)
176 def get_squash_msg(self):
177 return utils.slurp(self.__get_squash_msg_path())
179 def set_squash_msg(self):
180 self.model.set_commitmsg(self.model.get_squash_msg())
182 def get_prev_commitmsg(self,*rest):
183 '''Queries git for the latest commit message and sets it in
184 self.commitmsg.'''
185 commit_msg = []
186 commit_lines = git.show('HEAD').split('\n')
187 for idx, msg in enumerate(commit_lines):
188 if idx < 4: continue
189 msg = msg.lstrip()
190 if msg.startswith('diff --git'):
191 commit_msg.pop()
192 break
193 commit_msg.append(msg)
194 self.set_commitmsg(os.linesep.join(commit_msg).rstrip())
196 def update_status(self):
197 # This allows us to defer notification until the
198 # we finish processing data
199 notify_enabled = self.get_notify()
200 self.set_notify(False)
202 # Reset the staged and unstaged model lists
203 # NOTE: the model's unstaged list is used to
204 # hold both unstaged and untracked files.
205 self.staged = []
206 self.unstaged = []
207 self.untracked = []
209 # Read git status items
210 ( staged_items,
211 unstaged_items,
212 untracked_items ) = git.status()
214 # Gather items to be committed
215 for staged in staged_items:
216 if staged not in self.get_staged():
217 self.add_staged(staged)
219 # Gather unindexed items
220 for unstaged in unstaged_items:
221 if unstaged not in self.get_unstaged():
222 self.add_unstaged(unstaged)
224 # Gather untracked items
225 for untracked in untracked_items:
226 if untracked not in self.get_untracked():
227 self.add_untracked(untracked)
229 self.set_branch(git.current_branch())
230 self.set_all_unstaged(self.get_unstaged() + self.get_untracked())
231 self.set_remotes(git.remote())
232 self.set_remote_branches(git.branch(remote=True))
233 self.set_local_branches(git.branch(remote=False))
234 self.set_tags(git.tag())
235 self.set_revision('')
236 self.set_local_branch('')
237 self.set_remote_branch('')
239 # Re-enable notifications and emit changes
240 self.set_notify(notify_enabled)
241 self.notify_observers(
242 'branch', 'all_unstaged', 'staged',
243 'revision', 'remote', 'remotes',
244 'local_branches','remote_branches', 'tags')
246 def delete_branch(self, branch):
247 return git.branch(name=branch, delete=True)
249 def get_revision_sha1(self, idx):
250 return self.get_revisions()[idx]
252 def get_commit_diff(self, sha1):
253 commit = self.show(sha1)
254 first_newline = commit.index(os.linesep)
255 merge = commit[first_newline+1:].startswith('Merge:')
256 if merge:
257 return (commit + os.linesep*2
258 + self.diff(commit=sha1, cached=False,
259 suppress_header=False))
260 else:
261 return commit
263 def get_unstaged_item(self, idx):
264 return self.get_all_unstaged()[idx]
266 def get_diff_and_status(self, idx, staged=True):
267 if staged:
268 filename = self.get_staged()[idx]
269 if os.path.exists(filename):
270 status = 'Staged for commit'
271 else:
272 status = 'Staged for removal'
273 diff = self.diff(filename=filename, cached=True)
274 else:
275 filename = self.get_all_unstaged()[idx]
276 if os.path.isdir(filename):
277 status = 'Untracked directory'
278 diff = os.linesep.join(os.listdir(filename))
279 elif filename in self.get_unstaged():
280 status = 'Modified, not staged'
281 diff = self.diff(filename=filename, cached=False)
282 else:
283 status = 'Untracked, not staged'
285 file_type = utils.run_cmd('file','-b',filename)
286 if 'binary' in file_type or 'data' in file_type:
287 diff = utils.run_cmd('hexdump','-C',filename)
288 else:
289 if os.path.exists(filename):
290 file = open(filename, 'r')
291 diff = file.read()
292 file.close()
293 else:
294 diff = ''
295 return diff, status
297 def stage_changed(self):
298 git.add(self.get_unstaged())
299 self.update_status()
301 def stage_untracked(self):
302 git.add(self.get_untracked())
303 self.update_status()
305 def reset(self, items):
306 git.reset(items)
307 self.update_status()
309 def unstage_all(self):
310 git.reset(self.get_staged())
311 self.update_status()
313 def save_window_geom(self):
314 git.config('ugit.geometry', utils.get_geom())