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
)
26 #####################################################
27 # Used in various places
28 branch
= git
.current_branch(),
29 remotes
= git
.remote(),
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),
46 all_unstaged
= [], # unstaged+untracked
48 #####################################################
49 # Used by the create branch dialog
51 local_branches
= git
.branch(remote
=False),
52 remote_branches
= git
.branch(remote
=True),
55 #####################################################
56 # Used by the commit/repo browser
61 # These are parallel lists
66 # All items below here are re-calculated in
69 directory_entries
= {},
71 # These are also parallel lists
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
= []
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('([^/]+)/')
104 for idx
, name
in enumerate(self
.names
):
106 if not name
.startswith(self
.directory
): continue
107 name
= name
[ len(self
.directory
): ]
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
)
125 subdir
= entry_match
.group(1) + '/'
126 if subdir
in subdirs_seen
: continue
127 subdirs_seen
[subdir
] = True
128 dir_entries
[dirent
].append(subdir
)
130 dir_entries
[dirent
].append(entry
)
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
186 commit_lines
= git
.show('HEAD').split('\n')
187 for idx
, msg
in enumerate(commit_lines
):
190 if msg
.startswith('diff --git'):
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.
209 # Read git status 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:')
257 return (commit
+ os
.linesep
*2
258 + self
.diff(commit
=sha1
, cached
=False,
259 suppress_header
=False))
263 def get_unstaged_item(self
, idx
):
264 return self
.get_all_unstaged()[idx
]
266 def get_diff_and_status(self
, idx
, staged
=True):
268 filename
= self
.get_staged()[idx
]
269 if os
.path
.exists(filename
):
270 status
= 'Staged for commit'
272 status
= 'Staged for removal'
273 diff
= self
.diff(filename
=filename
, cached
=True)
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)
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
)
289 if os
.path
.exists(filename
):
290 file = open(filename
, 'r')
297 def stage_changed(self
):
298 git
.add(self
.get_unstaged())
301 def stage_untracked(self
):
302 git
.add(self
.get_untracked())
305 def reset(self
, items
):
309 def unstage_all(self
):
310 git
.reset(self
.get_staged())
313 def save_window_geom(self
):
314 git
.config('ugit.geometry', utils
.get_geom())