9 class Model(model
.Model
):
10 """Provides a friendly wrapper for doing commit git operations."""
13 """Reads git repository settings and sets severl methods
14 so that they refer to the git module. This object is
15 encapsulates ugit's interaction with git.
16 The git module itself should know nothing about ugit
19 # chdir to the root of the git tree.
20 # This keeps paths relative.
21 cdup
= git
.rev_parse(show_cdup
=True)
23 if cdup
.startswith('fatal:'):
24 # this is not a git repo
25 sys
.stderr
.write(cdup
+"\n")
30 self
.init_config_data()
32 # Import all git commands from git.py
33 for name
, cmd
in git
.commands
.iteritems():
34 setattr(self
, name
, cmd
)
37 #####################################################
38 # Used in various places
45 git_version
= git
.version(),
47 #####################################################
48 # Used primarily by the main UI
49 project
= os
.path
.basename(os
.getcwd()),
55 window_geom
= utils
.parse_geom(
56 self
.get_global_ugit_geometry()),
58 #####################################################
59 # Used by the create branch dialog
65 #####################################################
66 # Used by the commit/repo browser
71 # These are parallel lists
76 # All items below here are re-calculated in
79 directory_entries
= {},
81 # These are also parallel lists
88 def init_config_data(self
):
89 """Reads git config --list and creates parameters
91 # These parameters are saved in .gitconfig,
92 # so ideally these should be as short as possible.
94 # config items that are controllable globally
96 self
.__local
_and
_global
_defaults
= {
99 'merge_summary': False,
100 'merge_diffstat': True,
101 'merge_verbosity': 2,
102 'gui_diffcontext': 5,
103 'gui_pruneduringfetch': False,
105 # config items that are purely git config --global settings
106 self
.__global
_defaults
= {
109 'ugit_fontui_size':12,
111 'ugit_fontdiff_size':12,
112 'ugit_historybrowser': 'gitk',
113 'ugit_savewindowsettings': False,
114 'ugit_saveatexit': False,
117 local_dict
= git
.config_dict(local
=True)
118 global_dict
= git
.config_dict(local
=False)
120 for k
,v
in local_dict
.iteritems():
121 self
.set_param('local_'+k
, v
)
122 for k
,v
in global_dict
.iteritems():
123 self
.set_param('global_'+k
, v
)
124 if k
not in local_dict
:
126 self
.set_param('local_'+k
, v
)
128 # Bootstrap the internal font*_size variables
129 for param
in ('global_ugit_fontui', 'global_ugit_fontdiff'):
130 if hasattr(self
, param
):
131 font
= self
.get_param(param
)
133 size
= int(font
.split(',')[1])
134 self
.set_param(param
+'_size', size
)
135 param
= param
[len('global_'):]
136 global_dict
[param
] = font
137 global_dict
[param
+'_size'] = size
139 # Load defaults for all undefined items
140 local_and_global_defaults
= self
.__local
_and
_global
_defaults
141 for k
,v
in local_and_global_defaults
.iteritems():
142 if k
not in local_dict
:
143 self
.set_param('local_'+k
, v
)
144 if k
not in global_dict
:
145 self
.set_param('global_'+k
, v
)
147 global_defaults
= self
.__global
_defaults
148 for k
,v
in global_defaults
.iteritems():
149 if k
not in global_dict
:
150 self
.set_param('global_'+k
, v
)
152 def save_config_param(self
, param
):
153 if param
not in self
.get_config_params():
155 value
= self
.get_param(param
)
156 if param
== 'local_gui_diffcontext':
157 git
.DIFF_CONTEXT
= value
158 if param
.startswith('local_'):
159 param
= param
[len('local_'):]
161 elif param
.startswith('global_'):
162 param
= param
[len('global_'):]
165 raise Exception("Invalid param '%s' passed to " % param
166 + "save_config_param()")
167 param
= param
.replace('_','.') # model -> git
168 return git
.config_set(param
, value
, local
=is_local
)
170 def init_browser_data(self
):
171 '''This scans over self.(names, sha1s, types) to generate
172 directories, directory_entries, and subtree_*'''
174 # Collect data for the model
175 if not self
.get_currentbranch(): return
177 self
.subtree_types
= []
178 self
.subtree_sha1s
= []
179 self
.subtree_names
= []
180 self
.directories
= []
181 self
.directory_entries
= {}
183 # Lookup the tree info
184 tree_info
= git
.parse_ls_tree(self
.get_currentbranch())
186 self
.set_types(map( lambda(x
): x
[1], tree_info
))
187 self
.set_sha1s(map( lambda(x
): x
[2], tree_info
))
188 self
.set_names(map( lambda(x
): x
[3], tree_info
))
190 if self
.directory
: self
.directories
.append('..')
192 dir_entries
= self
.directory_entries
193 dir_regex
= re
.compile('([^/]+)/')
197 for idx
, name
in enumerate(self
.names
):
199 if not name
.startswith(self
.directory
): continue
200 name
= name
[ len(self
.directory
): ]
203 # This is a directory...
204 match
= dir_regex
.match(name
)
205 if not match
: continue
207 dirent
= match
.group(1) + '/'
208 if dirent
not in self
.directory_entries
:
209 self
.directory_entries
[dirent
] = []
211 if dirent
not in dirs_seen
:
212 dirs_seen
[dirent
] = True
213 self
.directories
.append(dirent
)
215 entry
= name
.replace(dirent
, '')
216 entry_match
= dir_regex
.match(entry
)
218 subdir
= entry_match
.group(1) + '/'
219 if subdir
in subdirs_seen
: continue
220 subdirs_seen
[subdir
] = True
221 dir_entries
[dirent
].append(subdir
)
223 dir_entries
[dirent
].append(entry
)
225 self
.subtree_types
.append(self
.types
[idx
])
226 self
.subtree_sha1s
.append(self
.sha1s
[idx
])
227 self
.subtree_names
.append(name
)
229 def get_history_browser(self
):
230 return self
.get_param('global_ugit_historybrowser')
232 def remember_gui_settings(self
):
233 return self
.get_param('global_ugit_savewindowsettings')
235 def save_at_exit(self
):
236 return self
.get_param('global_ugit_saveatexit')
238 def get_tree_node(self
, idx
):
239 return (self
.get_types()[idx
],
240 self
.get_sha1s()[idx
],
241 self
.get_names()[idx
] )
243 def get_subtree_node(self
, idx
):
244 return (self
.get_subtree_types()[idx
],
245 self
.get_subtree_sha1s()[idx
],
246 self
.get_subtree_names()[idx
] )
248 def get_all_branches(self
):
249 return (self
.get_local_branches() + self
.get_remote_branches())
251 def set_remote(self
, remote
):
252 if not remote
: return
253 self
.set_param('remote', remote
)
254 branches
= utils
.grep( '%s/\S+$' % remote
,
255 git
.branch_list(remote
=True), squash
=False)
256 self
.set_remote_branches(branches
)
258 def add_signoff(self
,*rest
):
259 '''Adds a standard Signed-off by: tag to the end
260 of the current commit message.'''
262 msg
= self
.get_commitmsg()
263 signoff
=('\n\nSigned-off-by: %s <%s>\n' % (
264 self
.get_local_user_name(),
265 self
.get_local_user_email()))
267 if signoff
not in msg
:
268 self
.set_commitmsg(msg
+ signoff
)
270 def apply_diff(self
, filename
):
271 return git
.apply(filename
, index
=True, cached
=True)
273 def load_commitmsg(self
, path
):
274 file = open(path
, 'r')
275 contents
= file.read()
277 self
.set_commitmsg(contents
)
279 def get_prev_commitmsg(self
,*rest
):
280 '''Queries git for the latest commit message and sets it in
283 commit_lines
= git
.show('HEAD').split('\n')
284 for idx
, msg
in enumerate(commit_lines
):
287 if msg
.startswith('diff --git'):
290 commit_msg
.append(msg
)
291 self
.set_commitmsg('\n'.join(commit_msg
).rstrip())
293 def update_status(self
):
294 # This allows us to defer notification until the
295 # we finish processing data
296 notify_enabled
= self
.get_notify()
297 self
.set_notify(False)
299 # Reset the staged and unstaged model lists
300 # NOTE: the model's unstaged list is used to
301 # hold both modified and untracked files.
306 # Read git status items
309 untracked_items
) = git
.parse_status()
311 # Gather items to be committed
312 for staged
in staged_items
:
313 if staged
not in self
.get_staged():
314 self
.add_staged(staged
)
316 # Gather unindexed items
317 for modified
in modified_items
:
318 if modified
not in self
.get_modified():
319 self
.add_modified(modified
)
321 # Gather untracked items
322 for untracked
in untracked_items
:
323 if untracked
not in self
.get_untracked():
324 self
.add_untracked(untracked
)
326 self
.set_currentbranch(git
.current_branch())
327 self
.set_unstaged(self
.get_modified() + self
.get_untracked())
328 self
.set_remotes(git
.remote().splitlines())
329 self
.set_remote_branches(git
.branch_list(remote
=True))
330 self
.set_local_branches(git
.branch_list(remote
=False))
331 self
.set_tags(git
.tag().splitlines())
332 self
.set_revision('')
333 self
.set_local_branch('')
334 self
.set_remote_branch('')
335 # Re-enable notifications and emit changes
336 self
.set_notify(notify_enabled
)
337 self
.notify_observers('staged','unstaged')
339 def delete_branch(self
, branch
):
340 return git
.branch(branch
, D
=True)
342 def get_revision_sha1(self
, idx
):
343 return self
.get_revisions()[idx
]
345 def get_config_params(self
):
347 params
.extend(map(lambda x
: 'local_' + x
,
348 self
.__local
_and
_global
_defaults
.keys()))
349 params
.extend(map(lambda x
: 'global_' + x
,
350 self
.__local
_and
_global
_defaults
.keys()))
351 params
.extend(map(lambda x
: 'global_' + x
,
352 self
.__global
_defaults
.keys()))
355 def apply_font_size(self
, param
, default
):
356 old_font
= self
.get_param(param
)
360 size
= self
.get_param(param
+'_size')
361 props
= old_font
.split(',')
363 new_font
= ','.join(props
)
365 self
.set_param(param
, new_font
)
367 def read_font_size(self
, param
, new_font
):
368 new_size
= int(new_font
.split(',')[1])
369 self
.set_param(param
, new_size
)
371 def get_commit_diff(self
, sha1
):
372 commit
= git
.show(sha1
)
373 first_newline
= commit
.index('\n')
374 if commit
[first_newline
+1:].startswith('Merge:'):
380 suppress_header
=False,
386 def get_diff_and_status(self
, idx
, staged
=True):
388 filename
= self
.get_staged()[idx
]
389 if os
.path
.exists(filename
):
390 status
= 'Staged for commit'
392 status
= 'Staged for removal'
393 diff
= self
.diff_helper(
398 filename
= self
.get_unstaged()[idx
]
399 if os
.path
.isdir(filename
):
400 status
= 'Untracked directory'
401 diff
= '\n'.join(os
.listdir(filename
))
402 elif filename
in self
.get_modified():
403 status
= 'Modified, not staged'
404 diff
= self
.diff_helper(
409 status
= 'Untracked, not staged'
411 file_type
= utils
.run_cmd('file',filename
, b
=True)
412 if 'binary' in file_type
or 'data' in file_type
:
413 diff
= utils
.run_cmd('hexdump', filename
, C
=True)
415 if os
.path
.exists(filename
):
416 file = open(filename
, 'r')
423 def stage_modified(self
):
424 output
= git
.add(self
.get_modified())
428 def stage_untracked(self
):
429 output
= git
.add(self
.get_untracked())
433 def reset(self
, *items
):
434 output
= git
.reset('--', *items
)
438 def unstage_all(self
):
439 git
.reset('--', *self
.get_staged())
442 def save_gui_settings(self
):
443 git
.config_set('ugit.geometry', utils
.get_geom(), local
=False)