8 class Model(model
.Model
):
10 model
.Model
.__init
__(self
)
12 # chdir to the root of the git tree. This is critical
13 # to being able to properly use the git porcelain.
14 cdup
= git
.show_cdup()
15 if cdup
: os
.chdir(cdup
)
18 #####################################################
19 # Used in various places
20 branch
= git
.current_branch(),
21 remotes
= git
.remote(),
26 #####################################################
27 # Used primarily by the main UI
28 window_geom
= utils
.parse_geom(git
.config('ugit.geometry')),
29 project
= os
.path
.basename(os
.getcwd()),
30 name
= git
.config('user.name'),
31 email
= git
.config('user.email'),
36 all_unstaged
= [], # unstaged+untracked
38 #####################################################
39 # Used by the create branch dialog
41 local_branches
= git
.branch(remote
=False),
42 remote_branches
= git
.branch(remote
=True),
45 #####################################################
46 # Used by the repo browser
49 # These are parallel lists
54 # All items below here are re-calculated in
57 directory_entries
= {},
59 # These are also parallel lists
65 # These methods are best left implemented in git.py
66 for attr
in ('add', 'add_or_remove', 'cat_file', 'checkout',
67 'create_branch', 'cherry_pick', 'commit', 'diff',
68 'diff_stat', 'format_patch', 'push', 'show','log',
69 'rebase', 'remote_url', 'rev_list_range'):
70 setattr(self
, attr
, getattr(git
,attr
))
72 def init_browser_data(self
):
73 '''This scans over self.(names, sha1s, types) to generate
74 directories, directory_entries, and subtree_*'''
76 # Collect data for the model
77 if not self
.get_branch(): return
79 self
.subtree_types
= []
80 self
.subtree_sha1s
= []
81 self
.subtree_names
= []
83 self
.directory_entries
= {}
85 # Lookup the tree info
86 tree_info
= git
.ls_tree(self
.get_branch())
88 self
.set_types(map( lambda(x
): x
[1], tree_info
))
89 self
.set_sha1s(map( lambda(x
): x
[2], tree_info
))
90 self
.set_names(map( lambda(x
): x
[3], tree_info
))
92 if self
.directory
: self
.directories
.append('..')
94 dir_entries
= self
.directory_entries
95 dir_regex
= re
.compile('([^/]+)/')
99 for idx
, name
in enumerate(self
.names
):
101 if not name
.startswith(self
.directory
): continue
102 name
= name
[ len(self
.directory
): ]
105 # This is a directory...
106 match
= dir_regex
.match(name
)
107 if not match
: continue
109 dirent
= match
.group(1) + '/'
110 if dirent
not in self
.directory_entries
:
111 self
.directory_entries
[dirent
] = []
113 if dirent
not in dirs_seen
:
114 dirs_seen
[dirent
] = True
115 self
.directories
.append(dirent
)
117 entry
= name
.replace(dirent
, '')
118 entry_match
= dir_regex
.match(entry
)
120 subdir
= entry_match
.group(1) + '/'
121 if subdir
in subdirs_seen
: continue
122 subdirs_seen
[subdir
] = True
123 dir_entries
[dirent
].append(subdir
)
125 dir_entries
[dirent
].append(entry
)
127 self
.subtree_types
.append(self
.types
[idx
])
128 self
.subtree_sha1s
.append(self
.sha1s
[idx
])
129 self
.subtree_names
.append(name
)
131 def get_tree_node(self
, idx
):
132 return (self
.get_types()[idx
],
133 self
.get_sha1s()[idx
],
134 self
.get_names()[idx
] )
136 def get_subtree_node(self
, idx
):
137 return (self
.get_subtree_types()[idx
],
138 self
.get_subtree_sha1s()[idx
],
139 self
.get_subtree_names()[idx
] )
141 def get_all_branches(self
):
142 return (self
.get_local_branches() + self
.get_remote_branches())
144 def set_remote(self
,remote
):
145 if not remote
: return
146 self
.set('remote',remote
)
147 branches
= utils
.grep( '%s/\S+$' % remote
, git
.branch(remote
=True))
148 self
.set_remote_branches(branches
)
150 def add_signoff(self
,*rest
):
151 '''Adds a standard Signed-off by: tag to the end
152 of the current commit message.'''
154 msg
= self
.get_commitmsg()
155 signoff
=('Signed-off by: %s <%s>'
156 %(self
.get_name(), self
.get_email()))
158 if signoff
not in msg
:
159 self
.set_commitmsg(msg
+ '\n\n' + signoff
)
161 def apply_diff(self
, filename
):
162 return git
.apply(filename
)
164 def __get_squash_msg_path(self
):
165 return os
.path
.join(os
.getcwd(), '.git', 'SQUASH_MSG')
167 def has_squash_msg(self
):
168 squash_msg
= self
.__get
_squash
_msg
_path
()
169 return os
.path
.exists(squash_msg
)
171 def get_squash_msg(self
):
172 return utils
.slurp(self
.__get
_squash
_msg
_path
())
174 def set_squash_msg(self
):
175 self
.model
.set_commitmsg(self
.model
.get_squash_msg())
177 def get_prev_commitmsg(self
,*rest
):
178 '''Queries git for the latest commit message and sets it in
181 commit_lines
= git
.show('HEAD').split('\n')
182 for idx
, msg
in enumerate(commit_lines
):
185 if msg
.startswith('diff --git'):
188 commit_msg
.append(msg
)
189 self
.set_commitmsg('\n'.join(commit_msg
).rstrip())
191 def update_status(self
):
192 # This allows us to defer notification until the
193 # we finish processing data
194 notify_enabled
= self
.get_notify()
195 self
.set_notify(False)
197 # Reset the staged and unstaged model lists
198 # NOTE: the model's unstaged list is used to
199 # hold both unstaged and untracked files.
204 # Read git status items
207 untracked_items
) = git
.status()
209 # Gather items to be committed
210 for staged
in staged_items
:
211 if staged
not in self
.get_staged():
212 self
.add_staged(staged
)
214 # Gather unindexed items
215 for unstaged
in unstaged_items
:
216 if unstaged
not in self
.get_unstaged():
217 self
.add_unstaged(unstaged
)
219 # Gather untracked items
220 for untracked
in untracked_items
:
221 if untracked
not in self
.get_untracked():
222 self
.add_untracked(untracked
)
224 self
.set_branch(git
.current_branch())
225 self
.set_all_unstaged(self
.get_unstaged() + self
.get_untracked())
226 self
.set_remotes(git
.remote())
227 self
.set_remote_branches(git
.branch(remote
=True))
228 self
.set_local_branches(git
.branch(remote
=False))
229 self
.set_tags(git
.tag())
230 self
.set_revision('')
231 self
.set_local_branch('')
232 self
.set_remote_branch('')
234 # Re-enable notifications and emit changes
235 self
.set_notify(notify_enabled
)
236 self
.notify_observers(
237 'branch', 'all_unstaged', 'staged',
238 'revision', 'remote', 'remotes',
239 'local_branches','remote_branches', 'tags')
241 def delete_branch(self
, branch
):
242 return git
.branch(name
=branch
, delete
=True)
244 def get_unstaged_item(self
, idx
):
245 return self
.get_all_unstaged()[idx
]
247 def get_diff_and_status(self
, idx
, staged
=True):
249 filename
= self
.get_staged()[idx
]
250 if os
.path
.exists(filename
):
251 status
= 'Staged for commit'
253 status
= 'Staged for removal'
254 diff
= self
.diff(filename
=filename
, cached
=True)
256 filename
= self
.get_all_unstaged()[idx
]
257 if os
.path
.isdir(filename
):
258 status
= 'Untracked directory'
259 diff
= os
.linesep
.join(os
.listdir(filename
))
260 elif filename
in self
.get_unstaged():
261 status
= 'Modified, not staged'
262 diff
= self
.diff(filename
=filename
, cached
=False)
264 status
= 'Untracked, not staged'
266 file_type
= git
.run_cmd('file','-b',filename
)
267 if 'binary' in file_type
or 'data' in file_type
:
268 diff
= git
.run_cmd('hexdump','-C',filename
)
270 if os
.path
.exists(filename
):
271 file = open(filename
, 'r')
278 def stage_changed(self
):
279 git
.add(self
.get_unstaged())
282 def stage_untracked(self
):
283 git
.add(self
.get_untracked())
286 def reset(self
, items
):
290 def unstage_all(self
):
291 git
.reset(self
.get_staged())
294 def save_window_geom(self
):
295 git
.config('ugit.geometry', utils
.get_geom())