8 class Model(model
.Model
):
10 model
.Model
.__init
__(self
)
11 self
.init_config_data()
12 # chdir to the root of the git tree.
13 # This keeps paths relative.
14 cdup
= git
.show_cdup()
15 if cdup
: os
.chdir(cdup
)
17 # These methods are best left implemented in git.py
36 setattr(self
, cmd
, getattr(git
,cmd
))
39 #####################################################
40 # Used in various places
41 branch
= git
.current_branch(),
42 remotes
= git
.remote(),
47 git_version
= git
.git('--version'),
49 #####################################################
50 # Used primarily by the main UI
51 window_geom
= utils
.parse_geom(
52 self
.get_param('global.ugit.geometry')),
53 project
= os
.path
.basename(os
.getcwd()),
58 all_unstaged
= [], # unstaged+untracked
60 #####################################################
61 # Used by the create branch dialog
63 local_branches
= git
.branch(remote
=False),
64 remote_branches
= git
.branch(remote
=True),
67 #####################################################
68 # Used by the commit/repo browser
73 # These are parallel lists
78 # All items below here are re-calculated in
81 directory_entries
= {},
83 # These are also parallel lists
89 def init_config_data(self
):
90 self
.__allowed
_params
= [
97 'gui.pruneduringfetch',
102 self
.__config
_types
= {}
103 self
.__config
_defaults
= {
106 'merge.summary': False,
107 'merge.diffstat': True,
108 'merge.verbosity': 2,
109 'gui.diffcontext': 5,
110 'gui.pruneduringfetch': False,
112 self
.__global
_defaults
= {
115 'ugit.fontui.size':12,
117 'ugit.fontdiff.size':12,
120 default_dict
= self
.__config
_defaults
121 if self
.__config
_types
: return
122 for k
,v
in default_dict
.iteritems():
124 self
.__config
_types
[k
] = 'int'
125 elif type(v
) is bool:
126 self
.__config
_types
[k
] = 'bool'
128 def config_to_dict(config
):
130 for line
in config
.splitlines():
131 k
, v
= line
.split('=')
133 linetype
= self
.__config
_types
[k
]
134 if linetype
== 'int':
136 elif linetype
== 'bool':
137 v
= bool(eval(v
.title()))
142 local_conf
= git
.git('config', '--list')
143 global_conf
= git
.git('config', '--global', '--list')
144 local_dict
= config_to_dict(local_conf
)
145 global_dict
= config_to_dict(global_conf
)
147 for k
,v
in local_dict
.iteritems():
148 self
.set_param('local.'+k
, v
)
149 for k
,v
in global_dict
.iteritems():
150 self
.set_param('global.'+k
, v
)
151 if k
not in local_dict
:
153 self
.set_param('local.'+k
, v
)
155 # internal bootstrap variables
156 for param
in ('global.ugit.fontui',
157 'global.ugit.fontdiff'):
158 if hasattr(self
, param
):
159 font
= self
.get_param(param
)
161 size
= int(font
.split(',')[1])
162 self
.set_param(param
+'.size', size
)
163 param
= param
[len('global.'):]
164 global_dict
[param
] = font
165 global_dict
[param
+'.size'] = size
167 # Load defaults for all undefined items
168 for k
,v
in default_dict
.iteritems():
169 if k
not in local_dict
:
170 self
.set_param('local.'+k
, v
)
171 if k
not in global_dict
:
172 self
.set_param('global.'+k
, v
)
174 for k
,v
in self
.__global
_defaults
.iteritems():
175 if k
not in global_dict
:
176 self
.set_param('global.'+k
, v
)
178 def save_config_param(self
,param
):
179 value
= self
.get_param(param
)
181 if param
.startswith('local.'):
182 param
= param
[len('local.'):]
184 elif param
.startswith('global.'):
185 param
= param
[len('global.'):]
187 if param
not in self
.__allowed
_params
:
189 git
.config(param
, value
, local
=is_local
)
190 if old_param
== 'local.gui.diffcontext':
192 self
.get_param('local.gui.diffcontext')
194 def init_browser_data(self
):
195 '''This scans over self.(names, sha1s, types) to generate
196 directories, directory_entries, and subtree_*'''
198 # Collect data for the model
199 if not self
.get_branch(): return
201 self
.subtree_types
= []
202 self
.subtree_sha1s
= []
203 self
.subtree_names
= []
204 self
.directories
= []
205 self
.directory_entries
= {}
207 # Lookup the tree info
208 tree_info
= git
.ls_tree(self
.get_branch())
210 self
.set_types(map( lambda(x
): x
[1], tree_info
))
211 self
.set_sha1s(map( lambda(x
): x
[2], tree_info
))
212 self
.set_names(map( lambda(x
): x
[3], tree_info
))
214 if self
.directory
: self
.directories
.append('..')
216 dir_entries
= self
.directory_entries
217 dir_regex
= re
.compile('([^/]+)/')
221 for idx
, name
in enumerate(self
.names
):
223 if not name
.startswith(self
.directory
): continue
224 name
= name
[ len(self
.directory
): ]
227 # This is a directory...
228 match
= dir_regex
.match(name
)
229 if not match
: continue
231 dirent
= match
.group(1) + '/'
232 if dirent
not in self
.directory_entries
:
233 self
.directory_entries
[dirent
] = []
235 if dirent
not in dirs_seen
:
236 dirs_seen
[dirent
] = True
237 self
.directories
.append(dirent
)
239 entry
= name
.replace(dirent
, '')
240 entry_match
= dir_regex
.match(entry
)
242 subdir
= entry_match
.group(1) + '/'
243 if subdir
in subdirs_seen
: continue
244 subdirs_seen
[subdir
] = True
245 dir_entries
[dirent
].append(subdir
)
247 dir_entries
[dirent
].append(entry
)
249 self
.subtree_types
.append(self
.types
[idx
])
250 self
.subtree_sha1s
.append(self
.sha1s
[idx
])
251 self
.subtree_names
.append(name
)
253 def get_tree_node(self
, idx
):
254 return (self
.get_types()[idx
],
255 self
.get_sha1s()[idx
],
256 self
.get_names()[idx
] )
258 def get_subtree_node(self
, idx
):
259 return (self
.get_subtree_types()[idx
],
260 self
.get_subtree_sha1s()[idx
],
261 self
.get_subtree_names()[idx
] )
263 def get_all_branches(self
):
264 return (self
.get_local_branches() + self
.get_remote_branches())
266 def set_remote(self
,remote
):
267 if not remote
: return
268 self
.set_param('remote',remote
)
269 branches
= utils
.grep( '%s/\S+$' % remote
,
270 git
.branch(remote
=True), squash
=False)
271 self
.set_remote_branches(branches
)
273 def add_signoff(self
,*rest
):
274 '''Adds a standard Signed-off by: tag to the end
275 of the current commit message.'''
277 msg
= self
.get_commitmsg()
278 signoff
=('Signed-off by: %s <%s>' % (
279 self
.get_param('local.user.name'),
280 self
.get_param('local.user.email')))
282 if signoff
not in msg
:
283 self
.set_commitmsg(msg
+ os
.linesep
*2 + signoff
)
285 def apply_diff(self
, filename
):
286 return git
.apply(filename
)
288 def __get_squash_msg_path(self
):
289 return os
.path
.join(os
.getcwd(), '.git', 'SQUASH_MSG')
291 def has_squash_msg(self
):
292 squash_msg
= self
.__get
_squash
_msg
_path
()
293 return os
.path
.exists(squash_msg
)
295 def get_squash_msg(self
):
296 return utils
.slurp(self
.__get
_squash
_msg
_path
())
298 def set_squash_msg(self
):
299 self
.set_commitmsg(self
.get_squash_msg())
301 def get_prev_commitmsg(self
,*rest
):
302 '''Queries git for the latest commit message and sets it in
305 commit_lines
= git
.show('HEAD').split('\n')
306 for idx
, msg
in enumerate(commit_lines
):
309 if msg
.startswith('diff --git'):
312 commit_msg
.append(msg
)
313 self
.set_commitmsg(os
.linesep
.join(commit_msg
).rstrip())
315 def update_status(self
):
316 # This allows us to defer notification until the
317 # we finish processing data
318 notify_enabled
= self
.get_notify()
319 self
.set_notify(False)
321 # Reset the staged and unstaged model lists
322 # NOTE: the model's unstaged list is used to
323 # hold both unstaged and untracked files.
328 # Read git status items
331 untracked_items
) = git
.status()
333 # Gather items to be committed
334 for staged
in staged_items
:
335 if staged
not in self
.get_staged():
336 self
.add_staged(staged
)
338 # Gather unindexed items
339 for unstaged
in unstaged_items
:
340 if unstaged
not in self
.get_unstaged():
341 self
.add_unstaged(unstaged
)
343 # Gather untracked items
344 for untracked
in untracked_items
:
345 if untracked
not in self
.get_untracked():
346 self
.add_untracked(untracked
)
348 self
.set_branch(git
.current_branch())
349 self
.set_all_unstaged(self
.get_unstaged() + self
.get_untracked())
350 self
.set_remotes(git
.remote())
351 self
.set_remote_branches(git
.branch(remote
=True))
352 self
.set_local_branches(git
.branch(remote
=False))
353 self
.set_tags(git
.tag())
354 self
.set_revision('')
355 self
.set_local_branch('')
356 self
.set_remote_branch('')
357 # Re-enable notifications and emit changes
358 self
.set_notify(notify_enabled
)
359 self
.notify_observers('all_unstaged', 'staged')
361 def delete_branch(self
, branch
):
362 return git
.branch(name
=branch
, delete
=True)
364 def get_revision_sha1(self
, idx
):
365 return self
.get_revisions()[idx
]
367 def set_local_config(self
, param
, val
):
368 self
.set_param(self
, 'local.'+param
, val
)
370 def set_global_config(self
, param
, val
):
371 self
.set_param(self
, 'global.'+param
, val
)
373 def get_config_params(self
):
375 params
.extend(map(lambda x
: 'local.' + x
,
376 self
.__config
_defaults
.keys()))
377 params
.extend(map(lambda x
: 'global.' + x
,
378 self
.__config
_defaults
.keys()))
379 params
.extend(map(lambda x
: 'global.' + x
,
380 self
.__global
_defaults
.keys()))
383 def apply_font_size(self
, param
, default
):
384 old_font
= self
.get_param(param
)
388 size
= self
.get_param(param
+'.size')
389 props
= old_font
.split(',')
391 new_font
= ','.join(props
)
393 self
.set_param(param
, new_font
)
395 def read_font_size(self
, param
, new_font
):
396 new_size
= int(new_font
.split(',')[1])
397 self
.set_param(param
, new_size
)
399 def get_commit_diff(self
, sha1
):
400 commit
= self
.show(sha1
)
401 first_newline
= commit
.index(os
.linesep
)
402 merge
= commit
[first_newline
+1:].startswith('Merge:')
404 return (commit
+ os
.linesep
*2
405 + self
.diff(commit
=sha1
, cached
=False,
406 suppress_header
=False))
410 def get_unstaged_item(self
, idx
):
411 return self
.get_all_unstaged()[idx
]
413 def get_diff_and_status(self
, idx
, staged
=True):
415 filename
= self
.get_staged()[idx
]
416 if os
.path
.exists(filename
):
417 status
= 'Staged for commit'
419 status
= 'Staged for removal'
420 diff
= self
.diff(filename
=filename
, cached
=True)
422 filename
= self
.get_all_unstaged()[idx
]
423 if os
.path
.isdir(filename
):
424 status
= 'Untracked directory'
425 diff
= os
.linesep
.join(os
.listdir(filename
))
426 elif filename
in self
.get_unstaged():
427 status
= 'Modified, not staged'
428 diff
= self
.diff(filename
=filename
, cached
=False)
430 status
= 'Untracked, not staged'
432 file_type
= utils
.run_cmd('file','-b',filename
)
433 if 'binary' in file_type
or 'data' in file_type
:
434 diff
= utils
.run_cmd('hexdump','-C',filename
)
436 if os
.path
.exists(filename
):
437 file = open(filename
, 'r')
444 def stage_changed(self
):
445 output
= git
.add(self
.get_unstaged())
449 def stage_untracked(self
):
450 output
= git
.add(self
.get_untracked())
454 def reset(self
, items
):
455 output
= git
.reset(items
)
459 def unstage_all(self
):
460 git
.reset(self
.get_staged())
463 def save_window_geom(self
):
464 git
.config('ugit.geometry', utils
.get_geom(), local
=False)