Added commit message menu, other fixes
[ugit.git] / ugitlibs / models.py
blob804124bad775cfc0561ca8c4eb6febdba30e1369
1 import os
2 import re
3 import commands
4 import cmds
5 import utils
6 from model import Model
8 class GitModel(Model):
9 def __init__(self):
10 Model.__init__(self, {
11 # ===========================================
12 # Used in various places
13 # ===========================================
14 'branch': '',
16 # ===========================================
17 # Used primarily by the main UI
18 # ===========================================
19 'name': cmds.git_config('user.name'),
20 'email': cmds.git_config('user.email'),
21 'commitmsg': '',
22 'staged': [],
23 'unstaged': [],
24 'untracked': [],
26 # ===========================================
27 # Used by the create branch dialog
28 # ===========================================
29 'revision': '',
30 'local_branches': cmds.git_branch(remote=False),
31 'remote_branches': cmds.git_branch(remote=True),
32 'tags': cmds.git_tag(),
34 # ===========================================
35 # Used by the repo browser
36 # ===========================================
37 'directory': '',
39 # These are parallel lists
40 'types': [],
41 'sha1s': [],
42 'names': [],
44 # All items below here are re-calculated in
45 # init_browser_data()
46 'directories': [],
47 'directory_entries': {},
49 # These are also parallel lists
50 'subtree_types': [],
51 'subtree_sha1s': [],
52 'subtree_names': [],
56 def all_branches(self):
57 return (self.get_local_branches()
58 + self.get_remote_branches())
60 def init_branch_data(self):
61 remote_branches = cmds.git_branch(remote=True)
62 local_branches = cmds.git_branch(remote=False)
63 tags = cmds.git_tag()
65 self.set_branch('')
66 self.set_revision('')
67 self.set_local_branches(local_branches)
68 self.set_remote_branches(remote_branches)
69 self.set_tags(tags)
71 def init_browser_data(self):
72 '''This scans over self.(names, sha1s, types) to generate
73 directories, directory_entries, and subtree_*'''
75 # Collect data for the model
76 if not self.get_branch(): return
78 self.subtree_types = []
79 self.subtree_sha1s = []
80 self.subtree_names = []
81 self.directories = []
82 self.directory_entries = {}
84 # Lookup the tree info
85 tree_info = cmds.git_ls_tree(self.get_branch())
87 self.set_types(map( lambda(x): x[1], tree_info ))
88 self.set_sha1s(map( lambda(x): x[2], tree_info ))
89 self.set_names(map( lambda(x): x[3], tree_info ))
91 if self.directory: self.directories.append('..')
93 dir_entries = self.directory_entries
94 dir_regex = re.compile('([^/]+)/')
95 dirs_seen = {}
96 subdirs_seen = {}
98 for idx, name in enumerate(self.names):
100 if not name.startswith(self.directory): continue
101 name = name[ len(self.directory): ]
103 if name.count('/'):
104 # This is a directory...
105 match = dir_regex.match(name)
106 if not match: continue
108 dirent = match.group(1) + '/'
109 if dirent not in self.directory_entries:
110 self.directory_entries[dirent] = []
112 if dirent not in dirs_seen:
113 dirs_seen[dirent] = True
114 self.directories.append(dirent)
116 entry = name.replace(dirent, '')
117 entry_match = dir_regex.match(entry)
118 if entry_match:
119 subdir = entry_match.group(1) + '/'
120 if subdir in subdirs_seen: continue
121 subdirs_seen[subdir] = True
122 dir_entries[dirent].append(subdir)
123 else:
124 dir_entries[dirent].append(entry)
125 else:
126 self.subtree_types.append(self.types[idx])
127 self.subtree_sha1s.append(self.sha1s[idx])
128 self.subtree_names.append(name)
130 def get_tree_node(self, idx):
131 return (self.get_types()[idx],
132 self.get_sha1s()[idx],
133 self.get_names()[idx] )
135 def get_subtree_node(self, idx):
136 return (self.get_subtree_types()[idx],
137 self.get_subtree_sha1s()[idx],
138 self.get_subtree_names()[idx] )
140 def add_signoff(self,*rest):
141 '''Adds a standard Signed-off by: tag to the end
142 of the current commit message.'''
144 msg = self.get_commitmsg()
145 signoff =('Signed-off by: %s <%s>'
146 %(self.get_name(), self.get_email()))
148 if signoff not in msg:
149 self.set_commitmsg(msg + '\n\n' + signoff)
151 def apply_diff(self, filename):
152 return cmds.git_apply(filename)
154 def get_uncommitted_item(self, row):
155 return(self.get_unstaged() + self.get_untracked())[row]
157 def __get_squash_msg_path(self):
158 return os.path.join(os.getcwd(), '.git', 'SQUASH_MSG')
160 def has_squash_msg(self):
161 squash_msg = self.__get_squash_msg_path()
162 return os.path.exists(squash_msg)
164 def get_squash_msg(self):
165 return utils.slurp(self.__get_squash_msg_path())
167 def get_prev_commitmsg(self,*rest):
168 '''Queries git for the latest commit message and sets it in
169 self.commitmsg.'''
170 commit_msg = []
171 commit_lines = cmds.git_show('HEAD').split('\n')
172 for idx, msg in enumerate(commit_lines):
173 if idx < 4: continue
174 msg = msg.lstrip()
175 if msg.startswith('diff --git'):
176 commit_msg.pop()
177 break
178 commit_msg.append(msg)
179 self.set_commitmsg('\n'.join(commit_msg).rstrip())
181 def update_status(self):
182 # This allows us to defer notification until the
183 # we finish processing data
184 notify_enabled = self.get_notify()
185 self.set_notify(False)
187 # Reset the staged and unstaged model lists
188 # NOTE: the model's unstaged list is used to
189 # hold both unstaged and untracked files.
190 self.staged = []
191 self.unstaged = []
192 self.untracked = []
194 # Read git status items
195 ( staged_items,
196 unstaged_items,
197 untracked_items ) = cmds.git_status()
199 # Gather items to be committed
200 for staged in staged_items:
201 if staged not in self.get_staged():
202 self.add_staged(staged)
204 # Gather unindexed items
205 for unstaged in unstaged_items:
206 if unstaged not in self.get_unstaged():
207 self.add_unstaged(unstaged)
209 # Gather untracked items
210 for untracked in untracked_items:
211 if untracked not in self.get_untracked():
212 self.add_untracked(untracked)
214 # Re-enable notifications and emit changes
215 self.set_notify(notify_enabled)
216 self.notify_observers('staged', 'unstaged')