citool.py is no more
[hgct.git] / git.py
blob39db70f966851a6d1617f2367c7e50d1e28444eb
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 getUnknownFiles():
75 args = []
77 if settings().gitExcludeFile():
78 if os.path.exists(settings().gitExcludeFile()):
79 args.append('--exclude-from=' + settings().gitExcludeFile())
80 if settings().gitExcludeDir():
81 args.append('--exclude-per-directory=' + settings().gitExcludeDir())
83 inp = runProgram(['git-ls-files', '-z', '--others'] + args)
84 files = inp.split("\0")
85 files.pop() # remove last entry (which is '')
87 fileObjects = []
88 for fileName in files:
89 f = File()
90 f.srcName = f.dstName = fileName
91 f.change = '?'
92 runProgram(['git-update-index', '--add', '--', fileName])
93 f.patch = runProgram(['git-diff-cache', '-p', '--cached', 'HEAD', '--', fileName])
94 runProgram(['git-update-index', '--force-remove', '--', fileName])
95 fileObjects.append(f)
96 f.text = 'New file: ' + fileName
98 return fileObjects
100 # HEAD is src in the returned File objects. That is, srcName is the
101 # name in HEAD and dstName is the name in the cache.
102 def getFiles():
103 files = parseDiff('git-diff-files -z')
104 for f in files:
105 doUpdateCache(f.srcName)
107 files = parseDiff('git-diff-cache -z -M --cached HEAD')
108 for f in files:
109 c = f.change
110 if c == 'C':
111 f.text = 'Copy from ' + f.srcName + ' to ' + f.dstName
112 elif c == 'R':
113 f.text = 'Rename from ' + f.srcName + ' to ' + f.dstName
114 elif c == 'A':
115 f.text = 'New file: ' + f.srcName
116 elif c == 'D':
117 f.text = 'Deleted file: ' + f.srcName
118 elif c == 'T':
119 f.text = 'Type change: ' + f.srcName
120 else:
121 f.text = f.srcName
123 if settings().gitShowUnknown:
124 files.extend(getUnknownFiles())
126 return files
128 def getPatch(file, otherFile = None):
129 if otherFile:
130 f = [file, otherFile]
131 else:
132 f = [file]
133 return runProgram(['git-diff-cache', '-p', '-M', '--cached', 'HEAD'] + f)
135 def doUpdateCache(filename):
136 runProgram(['git-update-index', '--remove', '--add', '--replace', '--', filename])
138 def doCommit(filesToKeep, filesToCommit, msg):
139 for file in filesToKeep:
140 # If we have a new file in the cache which we do not want to
141 # commit we have to remove it from the cache. We will add this
142 # cache entry back in to the cache at the end of this
143 # function.
144 if file.change == 'A':
145 runProgram(['git-update-index', '--force-remove',
146 '--', file.srcName])
147 elif file.change == 'R':
148 runProgram(['git-update-index', '--force-remove',
149 '--', file.dstName])
150 runProgram(['git-update-index', '--add', '--replace',
151 '--cacheinfo', file.srcMode, file.srcSHA, file.srcName])
152 elif file.change == '?':
153 pass
154 else:
155 runProgram(['git-update-index', '--add', '--replace',
156 '--cacheinfo', file.srcMode, file.srcSHA, file.srcName])
158 for file in filesToCommit:
159 if file.change == '?':
160 runProgram(['git-update-index', '--add', '--', file.dstName])
162 tree = runProgram(['git-write-tree'])
163 tree = tree.rstrip()
165 if commitIsMerge():
166 merge = ['-p', 'MERGE_HEAD']
167 else:
168 merge = []
169 commit = runProgram(['git-commit-tree', tree, '-p', 'HEAD'] + merge, msg)
171 try:
172 f = open(os.environ['GIT_DIR'] + '/HEAD', 'w+')
173 f.write(commit)
174 f.close()
175 except OSError, e:
176 raise CommitError('write to ' + os.environ['GIT_DIR'] + '/HEAD', e.strerror)
178 try:
179 os.unlink(os.environ['GIT_DIR'] + '/MERGE_HEAD')
180 except OSError:
181 pass
183 for file in filesToKeep:
184 # Don't add files that are going to be deleted back to the cache
185 if file.change != 'D' and file.change != '?':
186 runProgram(['git-update-index', '--add', '--replace', '--cacheinfo',
187 file.dstMode, file.dstSHA, file.dstName])
189 if file.change == 'R':
190 runProgram(['git-update-index', '--remove', '--', file.srcName])
192 def discardFile(file):
193 runProgram(['git-read-tree', 'HEAD'])
194 c = file.change
195 if c == 'M' or c == 'T':
196 runProgram(['git-checkout-cache', '-f', '-q', '--', file.dstName])
197 elif c == 'A' or c == 'C':
198 # The file won't be tracked by git now. We could unlink it
199 # from the working directory, but that seems a little bit
200 # too dangerous.
201 pass
202 elif c == 'D':
203 runProgram(['git-checkout-cache', '-f', '-q', '--', file.dstName])
204 elif c == 'R':
205 # Same comment applies here as to the 'A' or 'C' case.
206 runProgram(['git-checkout-cache', '-f', '-q', '--', file.srcName])
208 def ignoreFile(file):
209 ignoreExpr = re.sub(r'([][*?!\\])', r'\\\1', file.dstName)
211 excludefile = settings().gitExcludeFile()
212 excludefiledir = os.path.dirname(excludefile)
213 if not os.path.exists(excludefiledir):
214 os.mkdir(excludefiledir)
215 if not os.path.isdir(excludefiledir):
216 return
217 exclude = open(excludefile, 'a')
218 print >> exclude, ignoreExpr
219 exclude.close()
221 pass
223 def commitIsMerge():
224 try:
225 os.stat(os.environ['GIT_DIR'] + '/MERGE_HEAD')
226 return True
227 except OSError:
228 return False
230 def mergeMessage():
231 return '''This is a merge commit if you do not want to commit a ''' + \
232 '''merge remove the file $GIT_DIR/MERGE_HEAD.'''