Added interactive diff gui
[ugit.git] / py / models.py
blobf119652394072de3ea07f2a90860bfe04dd177b1
1 import os
2 import re
3 import commands
4 import cmds
5 from model import Model
7 class GitModel(Model):
8 def __init__ (self):
9 Model.__init__ (self, {
10 # ===========================================
11 # Used in various places
12 # ===========================================
13 'branch': '',
15 # ===========================================
16 # Used primarily by the main UI
17 # ===========================================
18 'name': cmds.git_config('user.name'),
19 'email': cmds.git_config('user.email'),
20 'commitmsg': '',
21 'staged': [],
22 'unstaged': [],
23 'untracked': [],
25 # ===========================================
26 # Used by the create branch dialog
27 # ===========================================
28 'revision': '',
29 'local_branches': cmds.git_branch (remote=False),
30 'remote_branches': cmds.git_branch (remote=True),
31 'tags': cmds.git_tag(),
33 # ===========================================
34 # Used by the repo browser
35 # ===========================================
36 'directory': '',
38 # These are parallel lists
39 'files': [],
40 'sha1s': [],
41 'types': [],
43 # All items below here are re-calculated in
44 # init_browser_data()
45 'directories': [],
46 'directory_entries': {},
48 # These are also parallel lists
49 'item_names': [],
50 'item_sha1s': [],
51 'item_types': [],
55 def init_branch_data (self):
56 remote_branches = cmds.git_branch (remote=True)
57 local_branches = cmds.git_branch (remote=False)
58 tags = cmds.git_tag()
60 self.set_branch ('')
61 self.set_revision ('')
62 self.set_local_branches (local_branches)
63 self.set_remote_branches (remote_branches)
64 self.set_tags (tags)
66 def init_browser_data (self):
67 '''This scans over self.(files, sha1s, types) to generate
68 directories, directory_entries, itmes, item_sha1s,
69 and item_types.'''
71 # Collect data for the model
72 if not self.get_branch(): return
74 self.item_names = []
75 self.item_sha1s = []
76 self.item_types = []
77 self.directories = []
78 self.directory_entries = {}
80 # Lookup the tree info
81 tree_info = cmds.git_ls_tree (self.get_branch())
83 self.set_types (map ( lambda (x): x[1], tree_info ))
84 self.set_sha1s (map ( lambda (x): x[2], tree_info ))
85 self.set_files (map ( lambda (x): x[3], tree_info ))
87 if self.directory: self.directories.append ('..')
89 dir_entries = self.directory_entries
90 dir_regex = re.compile ('([^/]+)/')
91 dirs_seen = {}
92 subdirs_seen = {}
94 for idx, file in enumerate (self.files):
96 orig_file = str (file)
97 if not orig_file.startswith (self.directory): continue
98 file = file[ len (self.directory): ]
100 if file.count ('/'):
101 # This is a directory...
102 match = dir_regex.match (file)
103 if not match: continue
105 dirent = match.group (1) + '/'
106 if dirent not in self.directory_entries:
107 self.directory_entries[dirent] = []
109 if dirent not in dirs_seen:
110 dirs_seen[dirent] = True
111 self.directories.append (dirent)
113 entry = file.replace (dirent, '')
114 entry_match = dir_regex.match (entry)
115 if entry_match:
116 subdir = entry_match.group (1) + '/'
117 if subdir in subdirs_seen: continue
118 subdirs_seen[subdir] = True
119 dir_entries[dirent].append (subdir)
120 else:
121 dir_entries[dirent].append (entry)
122 else:
123 self.item_names.append (file)
124 self.item_sha1s.append (self.sha1s[idx])
125 self.item_types.append (self.types[idx])
127 def add_signoff (self):
128 '''Adds a standard Signed-off by: tag to the end
129 of the current commit message.'''
131 msg = self.get_commitmsg()
132 signoff = ('Signed-off by: %s <%s>'
133 % (self.get_name(), self.get_email()))
135 if signoff not in msg:
136 self.set_commitmsg (msg + '\n\n' + signoff)
138 def apply_diff (self, filename):
139 return cmds.git_apply (filename)
141 def get_uncommitted_item (self, row):
142 return (self.get_unstaged() + self.get_untracked())[row]
144 def __get_squash_msg_path (self):
145 return os.path.join (os.getcwd(), '.git', 'SQUASH_MSG')
147 def has_squash_msg (self):
148 squash_msg = self.__get_squash_msg_path()
149 return os.path.exists (squash_msg)
151 def get_squash_msg (self):
152 squash_msg = self.__get_squash_msg_path()
153 file = open (squash_msg)
154 msg = file.read()
155 file.close()
156 return msg
158 def set_latest_commitmsg (self):
159 '''Queries git for the latest commit message and sets it in
160 self.commitmsg.'''
161 commit_msg = []
162 commit_lines = cmds.git_show ('HEAD').split ('\n')
163 for idx, msg in enumerate (commit_lines):
164 if idx < 4: continue
165 msg = msg.lstrip()
166 if msg.startswith ('diff --git'):
167 commit_msg.pop()
168 break
169 commit_msg.append (msg)
170 self.set_commitmsg ('\n'.join (commit_msg).rstrip())
172 def update_status (self):
173 # This allows us to defer notification until the
174 # we finish processing data
175 notify_enabled = self.get_notify()
176 self.set_notify(False)
178 # Reset the staged and unstaged model lists
179 # NOTE: the model's unstaged list is used to
180 # hold both unstaged and untracked files.
181 self.staged = []
182 self.unstaged = []
183 self.untracked = []
185 # Read git status items
186 ( staged_items,
187 unstaged_items,
188 untracked_items ) = cmds.git_status()
190 # Gather items to be committed
191 for staged in staged_items:
192 if staged not in self.get_staged():
193 self.add_staged (staged)
195 # Gather unindexed items
196 for unstaged in unstaged_items:
197 if unstaged not in self.get_unstaged():
198 self.add_unstaged (unstaged)
200 # Gather untracked items
201 for untracked in untracked_items:
202 if untracked not in self.get_untracked():
203 self.add_untracked (untracked)
205 # Re-enable notifications and emit changes
206 self.set_notify (notify_enabled)
207 self.notify_observers ('staged', 'unstaged')