8 '''Invokes 'git add' to index the filenames in to_add.'''
10 if not to_add
: return 'ERROR: No files to add.'
12 argv
= [ 'git', 'add' ]
13 for filename
in to_add
:
14 argv
.append (ugitutils
.shell_quote (filename
))
17 return 'Running:\t%s\n%s\n%s added successfully' % (
18 cmd
, commands
.getoutput (cmd
), ', '.join (to_add
) )
20 def git_add_or_remove (to_process
):
21 '''Invokes 'git add' to index the filenames in to_process that exist
22 and 'git rm' for those that do not exist.'''
24 if not to_process
: return 'ERROR: No files to add or remove.'
29 for filename
in to_process
:
30 if os
.path
.exists (filename
):
31 to_add
.append (filename
)
34 output
+= git_add (to_add
) + '\n\n'
36 if len(to_add
) == len(to_process
):
37 # to_process only contained unremoved files --
38 # short-circuit the removal checks
41 # Process files to add
42 argv
= [ 'git', 'rm' ]
43 for filename
in to_process
:
44 if not os
.path
.exists (filename
):
45 argv
.append (ugitutils
.shell_quote (filename
))
48 return output
+ 'Running: %s\n%s' % ( cmd
, commands
.getoutput (cmd
) )
51 '''Returns 'git branch''s output in a list.'''
52 return commands
.getoutput ('git branch').split ('\n')
54 def git_cherry_pick (revs
, commit
=False):
55 '''Cherry-picks each revision into the current branch.'''
57 return 'ERROR: No revisions selected for cherry-picking.'''
59 cmd = 'git cherry
-pick
'
60 if not commit: cmd += '-n
'
63 output.append ('Cherry
-picking
: ' + rev)
64 output.append (commands.getoutput (cmd + rev))
66 return '\n'.join (output)
68 def git_commit (msg, amend, commit_all, files):
69 '''Creates a git commit. 'commit_all
' triggers the -a
70 flag to 'git commit
.' 'amend
' triggers --amend.
71 'files
' is a list of files to use for commits without -a.'''
74 return 'ERROR
: No commit message was provided
.'
76 # Allow TMPDIR/TMP with a fallback to /tmp
77 tmpdir = os.getenv ('TMPDIR
', os.getenv ('TMP
', '/tmp
'))
79 # Sure, this is a potential "security risk," but if someone
80 # is trying to intercept/re-write commit messages on your system,
81 # then you probably have bigger problems to worry about.
82 tmpfile = os.path.join (tmpdir,
83 'ugit
.%s.%s' % ( os.getuid(), time.time() ))
85 argv = [ 'git
', 'commit
', '-F
', tmpfile ]
87 if amend: argv.append ('--amend
')
93 return 'ERROR
: No files selected
for commit
.'
97 argv.append (ugitutils.shell_quote (file))
99 # Create the commit message file
100 file = open (tmpfile, 'w
')
105 cmd = ' '.join (argv)
106 output = commands.getoutput (cmd)
109 return 'Running
:\t%s\n%s' % ( cmd, output )
111 def git_current_branch():
112 '''Parses 'git branch
' to find the current branch.'''
113 for branch in git_branch():
114 if branch.startswith ('* '):
115 return branch.lstrip ('* ')
116 raise Exception, 'No current branch
. Detached HEAD?
'
118 def git_diff (filename, staged=True):
119 '''Invokes git_diff on filename. Passing staged=True adds
120 diffs the index against HEAD (i.e. --cached).'''
123 argv = [ 'git
', 'diff
', '--color
']
125 deleted = not os.path.exists (filename)
126 argv.append ('--cached
')
129 argv.append (ugitutils.shell_quote (filename))
131 diff = commands.getoutput (' '.join (argv))
132 diff_lines = diff.split ('\n')
136 del_tag = 'deleted
file mode
'
138 for line in diff_lines:
139 if not start and '@@ ' in line and ' @@' in line:
141 if start or (deleted and del_tag in line):
143 return '\n'.join (output)
145 def git_diff_stat ():
146 '''Returns the latest diffstat.'''
147 return commands.getoutput ('git diff
--color
--stat HEAD^
')
149 def git_format_patch (revs, use_range):
150 '''Exports patches revs in the 'ugit
-patches
' subdirectory.
151 If use_range is True, a commit range is passed to git format-patch.'''
153 cmd = 'git format
-patch
--thread
--patch
-with
-stat
-o ugit
-patches
'
154 header = 'Generated Patches
:'
159 rev_range = '%s^
..%s' % ( revs[-1], revs[0] )
160 return header + '\n' + commands.getoutput (cmd + rev_range)
164 for idx, rev in enumerate (revs):
165 real_idx = idx + num_patches
166 revcmd = cmd + '-1 --start
-number
%d %s' % (real_idx, rev)
167 output.append (commands.getoutput (revcmd))
168 num_patches += output[-1].count ('\n')
169 return '\n'.join (output)
171 def git_log (oneline=True, all=False):
172 '''Returns a pair of parallel arrays listing the revision sha1's
173 and commit summaries
.'''
174 argv = [ 'git', 'log' ]
175 if oneline: argv.append ('--pretty=oneline')
176 if all: argv.append ('--all')
179 regex = re.compile ('(\w+)\W(.*)')
180 output = commands.getoutput (' '.join (argv))
181 for line in output.split ('\n'):
182 match = regex.match (line)
184 revs.append (match.group (1))
185 summaries.append (match.group (2))
186 return ( revs, summaries )
188 def git_reset (to_unstage):
189 '''Use
'git reset' to unstage files
from the index
.'''
191 if not to_unstage: return 'ERROR: No files to reset.'
193 argv = [ 'git', 'reset', '--' ]
194 for filename in to_unstage:
195 argv.append (ugitutils.shell_quote (filename))
197 cmd = ' '.join (argv)
198 return 'Running:\t%s\n%s' % ( cmd, commands.getoutput (cmd) )
200 def git_show (sha1, color=False):
202 if color: cmd += '--color '
203 return commands.getoutput (cmd + sha1)
206 '''Returns a relative path to the git project root
.'''
207 return commands.getoutput ('git rev-parse --show-cdup')
210 '''RETURNS
: A
tuple of staged
, unstaged
and untracked files
.
211 ( array(staged
), array(unstaged
), array(untracked
) )'''
213 status_lines = commands.getoutput ('git status').split ('\n')
215 unstaged_header_seen = False
216 untracked_header_seen = False
218 modified_header = '# Changed but not updated:'
219 modified_regex = re.compile('(#\tmodified:|#\tnew file:|#\tdeleted:)')
221 untracked_header = '# Untracked files:'
222 untracked_regex = re.compile ('#\t(.+)')
228 for status_line in status_lines:
229 if untracked_header in status_line:
230 untracked_header_seen = True
232 if not untracked_header_seen:
234 match = untracked_regex.match (status_line)
236 filename = match.group (1)
237 untracked.append (filename)
239 for status_line in status_lines:
240 if modified_header in status_line:
241 unstaged_header_seen = True
243 match = modified_regex.match (status_line)
245 tag = match.group (0)
246 filename = status_line.replace (tag, '')
247 if unstaged_header_seen:
248 unstaged.append (filename.lstrip())
250 staged.append (filename.lstrip())
252 return ( staged, unstaged, untracked )