Added a "git push" dialog
[ugit.git] / ugitlibs / models.py
blobea5c11755cdc7faf7501ed4610ee90f644761df8
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):
11 # chdir to the root of the git tree. This is critical
12 # to being able to properly use the git porcelain.
13 cdup = cmds.git_show_cdup()
14 if cdup: os.chdir(cdup)
16 Model.__init__(self, {
17 #####################################################
18 # Used in various places
19 'remotes': cmds.git_remote(),
20 'remote': '',
21 'local_branch': '',
22 'remote_branch': '',
24 #####################################################
25 # Used primarily by the main UI
26 'project': os.path.basename(os.getcwd()),
27 'name': cmds.git_config('user.name'),
28 'email': cmds.git_config('user.email'),
29 'commitmsg': '',
30 'staged': [],
31 'unstaged': [],
32 'untracked': [],
34 #####################################################
35 # Used by the create branch dialog
36 'revision': '',
37 'local_branches': cmds.git_branch(remote=False),
38 'remote_branches': cmds.git_branch(remote=True),
39 'tags': cmds.git_tag(),
41 #####################################################
42 # Used by the repo browser
43 'directory': '',
45 # These are parallel lists
46 'types': [],
47 'sha1s': [],
48 'names': [],
50 # All items below here are re-calculated in
51 # init_browser_data()
52 'directories': [],
53 'directory_entries': {},
55 # These are also parallel lists
56 'subtree_types': [],
57 'subtree_sha1s': [],
58 'subtree_names': [],
62 def all_branches(self):
63 return (self.get_local_branches()
64 + self.get_remote_branches())
66 def init_branch_data(self):
67 remotes = cmds.git_remote()
68 remote_branches = cmds.git_branch(remote=True)
69 local_branches = cmds.git_branch(remote=False)
70 tags = cmds.git_tag()
72 self.set_revision('')
73 self.set_local_branch('')
74 self.set_remote_branch('')
75 self.set_remotes(remotes)
76 self.set_remote_branches(remote_branches)
77 self.set_local_branches(local_branches)
78 self.set_tags(tags)
80 def init_browser_data(self):
81 '''This scans over self.(names, sha1s, types) to generate
82 directories, directory_entries, and subtree_*'''
84 # Collect data for the model
85 if not self.get_branch(): return
87 self.subtree_types = []
88 self.subtree_sha1s = []
89 self.subtree_names = []
90 self.directories = []
91 self.directory_entries = {}
93 # Lookup the tree info
94 tree_info = cmds.git_ls_tree(self.get_branch())
96 self.set_types(map( lambda(x): x[1], tree_info ))
97 self.set_sha1s(map( lambda(x): x[2], tree_info ))
98 self.set_names(map( lambda(x): x[3], tree_info ))
100 if self.directory: self.directories.append('..')
102 dir_entries = self.directory_entries
103 dir_regex = re.compile('([^/]+)/')
104 dirs_seen = {}
105 subdirs_seen = {}
107 for idx, name in enumerate(self.names):
109 if not name.startswith(self.directory): continue
110 name = name[ len(self.directory): ]
112 if name.count('/'):
113 # This is a directory...
114 match = dir_regex.match(name)
115 if not match: continue
117 dirent = match.group(1) + '/'
118 if dirent not in self.directory_entries:
119 self.directory_entries[dirent] = []
121 if dirent not in dirs_seen:
122 dirs_seen[dirent] = True
123 self.directories.append(dirent)
125 entry = name.replace(dirent, '')
126 entry_match = dir_regex.match(entry)
127 if entry_match:
128 subdir = entry_match.group(1) + '/'
129 if subdir in subdirs_seen: continue
130 subdirs_seen[subdir] = True
131 dir_entries[dirent].append(subdir)
132 else:
133 dir_entries[dirent].append(entry)
134 else:
135 self.subtree_types.append(self.types[idx])
136 self.subtree_sha1s.append(self.sha1s[idx])
137 self.subtree_names.append(name)
139 def get_tree_node(self, idx):
140 return (self.get_types()[idx],
141 self.get_sha1s()[idx],
142 self.get_names()[idx] )
144 def get_subtree_node(self, idx):
145 return (self.get_subtree_types()[idx],
146 self.get_subtree_sha1s()[idx],
147 self.get_subtree_names()[idx] )
149 def add_signoff(self,*rest):
150 '''Adds a standard Signed-off by: tag to the end
151 of the current commit message.'''
153 msg = self.get_commitmsg()
154 signoff =('Signed-off by: %s <%s>'
155 %(self.get_name(), self.get_email()))
157 if signoff not in msg:
158 self.set_commitmsg(msg + '\n\n' + signoff)
160 def apply_diff(self, filename):
161 return cmds.git_apply(filename)
163 def get_uncommitted_item(self, row):
164 return(self.get_unstaged() + self.get_untracked())[row]
166 def __get_squash_msg_path(self):
167 return os.path.join(os.getcwd(), '.git', 'SQUASH_MSG')
169 def has_squash_msg(self):
170 squash_msg = self.__get_squash_msg_path()
171 return os.path.exists(squash_msg)
173 def get_squash_msg(self):
174 return utils.slurp(self.__get_squash_msg_path())
176 def get_prev_commitmsg(self,*rest):
177 '''Queries git for the latest commit message and sets it in
178 self.commitmsg.'''
179 commit_msg = []
180 commit_lines = cmds.git_show('HEAD').split('\n')
181 for idx, msg in enumerate(commit_lines):
182 if idx < 4: continue
183 msg = msg.lstrip()
184 if msg.startswith('diff --git'):
185 commit_msg.pop()
186 break
187 commit_msg.append(msg)
188 self.set_commitmsg('\n'.join(commit_msg).rstrip())
190 def update_status(self):
191 # This allows us to defer notification until the
192 # we finish processing data
193 notify_enabled = self.get_notify()
194 self.set_notify(False)
196 # Reset the staged and unstaged model lists
197 # NOTE: the model's unstaged list is used to
198 # hold both unstaged and untracked files.
199 self.staged = []
200 self.unstaged = []
201 self.untracked = []
203 # Read git status items
204 ( staged_items,
205 unstaged_items,
206 untracked_items ) = cmds.git_status()
208 # Gather items to be committed
209 for staged in staged_items:
210 if staged not in self.get_staged():
211 self.add_staged(staged)
213 # Gather unindexed items
214 for unstaged in unstaged_items:
215 if unstaged not in self.get_unstaged():
216 self.add_unstaged(unstaged)
218 # Gather untracked items
219 for untracked in untracked_items:
220 if untracked not in self.get_untracked():
221 self.add_untracked(untracked)
223 # Re-enable notifications and emit changes
224 self.set_notify(notify_enabled)
225 self.notify_observers('staged', 'unstaged')