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 name
= git
.config('user.name'),
39 email
= git
.config('user.email'),
44 all_unstaged
= [], # unstaged+untracked
46 #####################################################
47 # Used by the create branch dialog
49 local_branches
= git
.branch(remote
=False),
50 remote_branches
= git
.branch(remote
=True),
53 #####################################################
54 # Used by the commit/repo browser
59 # These are parallel lists
64 # All items below here are re-calculated in
67 directory_entries
= {},
69 # These are also parallel lists
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
= []
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('([^/]+)/')
102 for idx
, name
in enumerate(self
.names
):
104 if not name
.startswith(self
.directory
): continue
105 name
= name
[ len(self
.directory
): ]
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
)
123 subdir
= entry_match
.group(1) + '/'
124 if subdir
in subdirs_seen
: continue
125 subdirs_seen
[subdir
] = True
126 dir_entries
[dirent
].append(subdir
)
128 dir_entries
[dirent
].append(entry
)
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
184 commit_lines
= git
.show('HEAD').split('\n')
185 for idx
, msg
in enumerate(commit_lines
):
188 if msg
.startswith('diff --git'):
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.
207 # Read git status 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:')
255 return (commit
+ os
.linesep
*2
256 + self
.diff(commit
=sha1
, cached
=False,
257 suppress_header
=False))
261 def get_unstaged_item(self
, idx
):
262 return self
.get_all_unstaged()[idx
]
264 def get_diff_and_status(self
, idx
, staged
=True):
266 filename
= self
.get_staged()[idx
]
267 if os
.path
.exists(filename
):
268 status
= 'Staged for commit'
270 status
= 'Staged for removal'
271 diff
= self
.diff(filename
=filename
, cached
=True)
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)
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
)
287 if os
.path
.exists(filename
):
288 file = open(filename
, 'r')
295 def stage_changed(self
):
296 git
.add(self
.get_unstaged())
299 def stage_untracked(self
):
300 git
.add(self
.get_untracked())
303 def reset(self
, items
):
307 def unstage_all(self
):
308 git
.reset(self
.get_staged())
311 def save_window_geom(self
):
312 git
.config('ugit.geometry', utils
.get_geom())