When invoked as "hg ct" chdir automatically to the repo root before running the commi...
[hgct.git] / git.py
blob651363e34cb75f9a5912d3dc3e1163d2b51a91ed
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-cache', '--add', '--', fileName])
93 f.patch = runProgram(['git-diff-cache', '-p', '--cached', 'HEAD', '--', fileName])
94 runProgram(['git-update-cache', '--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 return files + getUnknownFiles()
125 def getPatch(file, otherFile = None):
126 if otherFile:
127 f = [file, otherFile]
128 else:
129 f = [file]
130 return runProgram(['git-diff-cache', '-p', '-M', '--cached', 'HEAD'] + f)
132 def doUpdateCache(filename):
133 runProgram(['git-update-cache', '--remove', '--add', '--replace', '--', filename])
135 def doCommit(filesToKeep, filesToCommit, msg):
136 for file in filesToKeep:
137 # If we have a new file in the cache which we do not want to
138 # commit we have to remove it from the cache. We will add this
139 # cache entry back in to the cache at the end of this
140 # function.
141 if file.change == 'A':
142 runProgram(['git-update-cache', '--force-remove',
143 '--', file.srcName])
144 elif file.change == 'R':
145 runProgram(['git-update-cache', '--force-remove',
146 '--', file.dstName])
147 runProgram(['git-update-cache', '--add', '--replace',
148 '--cacheinfo', file.srcMode, file.srcSHA, file.srcName])
149 elif file.change == '?':
150 pass
151 else:
152 runProgram(['git-update-cache', '--add', '--replace',
153 '--cacheinfo', file.srcMode, file.srcSHA, file.srcName])
155 for file in filesToCommit:
156 if file.change == '?':
157 runProgram(['git-update-cache', '--add', '--', file.dstName])
159 tree = runProgram(['git-write-tree'])
160 tree = tree.rstrip()
162 if commitIsMerge():
163 merge = ['-p', 'MERGE_HEAD']
164 else:
165 merge = []
166 commit = runProgram(['git-commit-tree', tree, '-p', 'HEAD'] + merge, msg)
168 try:
169 f = open(os.environ['GIT_DIR'] + '/HEAD', 'w+')
170 f.write(commit)
171 f.close()
172 except OSError, e:
173 raise CommitError('write to ' + os.environ['GIT_DIR'] + '/HEAD', e.strerror)
175 try:
176 os.unlink(os.environ['GIT_DIR'] + '/MERGE_HEAD')
177 except OSError:
178 pass
180 for file in filesToKeep:
181 # Don't add files that are going to be deleted back to the cache
182 if file.change != 'D' and file.change != '?':
183 runProgram(['git-update-cache', '--add', '--replace', '--cacheinfo',
184 file.dstMode, file.dstSHA, file.dstName])
186 if file.change == 'R':
187 runProgram(['git-update-cache', '--remove', '--', file.srcName])
189 def discardFile(file):
190 runProgram(['git-read-tree', 'HEAD'])
191 c = file.change
192 if c == 'M' or c == 'T':
193 runProgram(['git-checkout-cache', '-f', '-q', '--', file.dstName])
194 elif c == 'A' or c == 'C':
195 # The file won't be tracked by git now. We could unlink it
196 # from the working directory, but that seems a little bit
197 # too dangerous.
198 pass
199 elif c == 'D':
200 runProgram(['git-checkout-cache', '-f', '-q', '--', file.dstName])
201 elif c == 'R':
202 # Same comment applies here as to the 'A' or 'C' case.
203 runProgram(['git-checkout-cache', '-f', '-q', '--', file.srcName])
205 def ignoreFile(file):
206 ignoreExpr = re.sub(r'([][*?!\\])', r'\\\1', file.dstName)
208 excludefile = settings().gitExcludeFile()
209 excludefiledir = os.path.dirname(excludefile)
210 if not os.path.exists(excludefiledir):
211 os.mkdir(excludefiledir)
212 if not os.path.isdir(excludefiledir):
213 return
214 exclude = open(excludefile, 'a')
215 print >> exclude, ignoreExpr
216 exclude.close()
218 pass
220 def commitIsMerge():
221 try:
222 os.stat(os.environ['GIT_DIR'] + '/MERGE_HEAD')
223 return True
224 except OSError:
225 return False
227 def mergeMessage():
228 return '''This is a merge commit if you do not want to commit a ''' + \
229 '''merge remove the file $GIT_DIR/MERGE_HEAD.'''