Make the ProgramError class printable.
[hgct.git] / git.py
blobc366e3e176bc97dcda68e25186f377eebf83d2b4
1 # Copyright (c) 2005 Fredrik Kuivinen <freku045@student.liu.se>
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License version 2 as
5 # published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
12 # You should have received a copy of the GNU General Public License
13 # along with this program; if not, write to the Free Software
14 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 import sys, os, re
18 from ctcore import *
20 def repoValid():
21 if not os.environ.has_key('GIT_DIR'):
22 os.environ['GIT_DIR'] = '.git'
24 if not os.environ.has_key('GIT_OBJECT_DIRECTORY'):
25 os.environ['GIT_OBJECT_DIRECTORY'] = os.environ['GIT_DIR'] + '/objects'
27 if not (os.path.exists(os.environ['GIT_DIR']) and
28 os.path.exists(os.environ['GIT_DIR'] + '/refs') and
29 os.path.exists(os.environ['GIT_OBJECT_DIRECTORY']) and
30 os.path.exists(os.environ['GIT_OBJECT_DIRECTORY'] + '/00')):
31 print "Git archive not found."
32 print "Make sure that the current working directory contains a '.git' directory, or\nthat GIT_DIR is set appropriately."
33 sys.exit(1)
35 parseDiffRE = re.compile(':([0-9]+) ([0-9]+) ([0-9a-f]{40}) ([0-9a-f]{40}) ([MCRNADUT])([0-9]*)')
36 def parseDiff(prog):
37 inp = runProgram(prog)
38 ret = []
39 try:
40 recs = inp.split("\0")
41 recs.pop() # remove last entry (which is '')
42 it = recs.__iter__()
43 while True:
44 rec = it.next()
45 m = parseDiffRE.match(rec)
47 if not m:
48 print "Unknown output from " + str(prog) + "!: " + rec + "\n"
49 continue
51 f = File()
52 f.srcMode = m.group(1)
53 f.dstMode = m.group(2)
54 f.srcSHA = m.group(3)
55 f.dstSHA = m.group(4)
56 if m.group(5) == 'N':
57 f.change = 'A'
58 else:
59 f.change = m.group(5)
60 f.score = m.group(6)
61 f.srcName = f.dstName = it.next()
63 if f.change == 'C' or f.change == 'R':
64 f.dstName = it.next()
65 f.patch = getPatch(f.srcName, f.dstName)
66 else:
67 f.patch = getPatch(f.srcName)
69 ret.append(f)
70 except StopIteration:
71 pass
72 return ret
74 def runXargsStyle(origProg, args):
75 steps = range(10, len(args), 10)
76 prog = origProg[:]
77 prev = 0
78 for i in steps:
79 prog.extend(args[prev:i])
80 runProgram(prog)
81 prog = origProg[:]
82 prev = i
84 prog.extend(args[prev:])
85 runProgram(prog)
87 def getUnknownFiles():
88 args = []
90 if settings().gitExcludeFile():
91 if os.path.exists(settings().gitExcludeFile()):
92 args.append('--exclude-from=' + settings().gitExcludeFile())
93 if settings().gitExcludeDir():
94 args.append('--exclude-per-directory=' + settings().gitExcludeDir())
96 inp = runProgram(['git-ls-files', '-z', '--others'] + args)
97 files = inp.split("\0")
98 files.pop() # remove last entry (which is '')
100 fileObjects = []
102 runXargsStyle(['git-update-index', '--add', '--'], files)
103 for fileName in files:
104 f = File()
105 f.srcName = f.dstName = fileName
106 f.change = '?'
107 f.patch = runProgram(['git-diff-cache', '-p', '--cached', 'HEAD', '--', fileName])
108 fileObjects.append(f)
109 f.text = 'New file: ' + fileName
111 runXargsStyle(['git-update-index', '--force-remove', '--'], files)
112 return fileObjects
114 # HEAD is src in the returned File objects. That is, srcName is the
115 # name in HEAD and dstName is the name in the cache.
116 def getFiles():
117 files = parseDiff('git-diff-files -z')
118 for f in files:
119 doUpdateCache(f.srcName)
121 files = parseDiff('git-diff-cache -z -M --cached HEAD')
122 for f in files:
123 c = f.change
124 if c == 'C':
125 f.text = 'Copy from ' + f.srcName + ' to ' + f.dstName
126 elif c == 'R':
127 f.text = 'Rename from ' + f.srcName + ' to ' + f.dstName
128 elif c == 'A':
129 f.text = 'New file: ' + f.srcName
130 elif c == 'D':
131 f.text = 'Deleted file: ' + f.srcName
132 elif c == 'T':
133 f.text = 'Type change: ' + f.srcName
134 else:
135 f.text = f.srcName
137 if settings().gitShowUnknown:
138 files.extend(getUnknownFiles())
140 return files
142 def getPatch(file, otherFile = None):
143 if otherFile:
144 f = [file, otherFile]
145 else:
146 f = [file]
147 return runProgram(['git-diff-cache', '-p', '-M', '--cached', 'HEAD'] + f)
149 def doUpdateCache(filename):
150 runProgram(['git-update-index', '--remove', '--add', '--replace', '--', filename])
152 def doCommit(filesToKeep, filesToCommit, msg):
153 for file in filesToKeep:
154 # If we have a new file in the cache which we do not want to
155 # commit we have to remove it from the cache. We will add this
156 # cache entry back in to the cache at the end of this
157 # function.
158 if file.change == 'A':
159 runProgram(['git-update-index', '--force-remove',
160 '--', file.srcName])
161 elif file.change == 'R':
162 runProgram(['git-update-index', '--force-remove',
163 '--', file.dstName])
164 runProgram(['git-update-index', '--add', '--replace',
165 '--cacheinfo', file.srcMode, file.srcSHA, file.srcName])
166 elif file.change == '?':
167 pass
168 else:
169 runProgram(['git-update-index', '--add', '--replace',
170 '--cacheinfo', file.srcMode, file.srcSHA, file.srcName])
172 for file in filesToCommit:
173 if file.change == '?':
174 runProgram(['git-update-index', '--add', '--', file.dstName])
176 tree = runProgram(['git-write-tree'])
177 tree = tree.rstrip()
179 if commitIsMerge():
180 merge = ['-p', 'MERGE_HEAD']
181 else:
182 merge = []
183 commit = runProgram(['git-commit-tree', tree, '-p', 'HEAD'] + merge, msg)
185 try:
186 f = open(os.environ['GIT_DIR'] + '/HEAD', 'w+')
187 f.write(commit)
188 f.close()
189 except OSError, e:
190 raise CommitError('write to ' + os.environ['GIT_DIR'] + '/HEAD', e.strerror)
192 try:
193 os.unlink(os.environ['GIT_DIR'] + '/MERGE_HEAD')
194 except OSError:
195 pass
197 for file in filesToKeep:
198 # Don't add files that are going to be deleted back to the cache
199 if file.change != 'D' and file.change != '?':
200 runProgram(['git-update-index', '--add', '--replace', '--cacheinfo',
201 file.dstMode, file.dstSHA, file.dstName])
203 if file.change == 'R':
204 runProgram(['git-update-index', '--remove', '--', file.srcName])
206 def discardFile(file):
207 runProgram(['git-read-tree', 'HEAD'])
208 c = file.change
209 if c == 'M' or c == 'T':
210 runProgram(['git-checkout-cache', '-f', '-q', '--', file.dstName])
211 elif c == 'A' or c == 'C':
212 # The file won't be tracked by git now. We could unlink it
213 # from the working directory, but that seems a little bit
214 # too dangerous.
215 pass
216 elif c == 'D':
217 runProgram(['git-checkout-cache', '-f', '-q', '--', file.dstName])
218 elif c == 'R':
219 # Same comment applies here as to the 'A' or 'C' case.
220 runProgram(['git-checkout-cache', '-f', '-q', '--', file.srcName])
222 def ignoreFile(file):
223 ignoreExpr = re.sub(r'([][*?!\\])', r'\\\1', file.dstName)
225 excludefile = settings().gitExcludeFile()
226 excludefiledir = os.path.dirname(excludefile)
227 if not os.path.exists(excludefiledir):
228 os.mkdir(excludefiledir)
229 if not os.path.isdir(excludefiledir):
230 return
231 exclude = open(excludefile, 'a')
232 print >> exclude, ignoreExpr
233 exclude.close()
235 pass
237 def commitIsMerge():
238 try:
239 os.stat(os.environ['GIT_DIR'] + '/MERGE_HEAD')
240 return True
241 except OSError:
242 return False
244 def mergeMessage():
245 return '''This is a merge commit if you do not want to commit a ''' + \
246 '''merge remove the file $GIT_DIR/MERGE_HEAD.'''