Merge http://www.cl.cam.ac.uk/~maw48/gct.git
[hgct.git] / git.py
blobe546e5ec6fd2c530d7a8c1994ccc9c1e21730033
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
75 # HEAD is src in the returned File objects. That is, srcName is the
76 # name in HEAD and dstName is the name in the cache.
77 def getFiles():
78 files = parseDiff('git-diff-files -z')
79 for f in files:
80 doUpdateCache(f.srcName)
82 files = parseDiff('git-diff-cache -z -M --cached HEAD')
83 for f in files:
84 c = f.change
85 if c == 'C':
86 f.text = 'Copy from ' + f.srcName + ' to ' + f.dstName
87 elif c == 'R':
88 f.text = 'Rename from ' + f.srcName + ' to ' + f.dstName
89 elif c == 'A':
90 f.text = 'New file: ' + f.srcName
91 elif c == 'D':
92 f.text = 'Deleted file: ' + f.srcName
93 elif c == 'T':
94 f.text = 'Type change: ' + f.srcName
95 else:
96 f.text = f.srcName
98 return files
100 def getPatch(file, otherFile = None):
101 if otherFile:
102 f = [file, otherFile]
103 else:
104 f = [file]
105 return runProgram(['git-diff-cache', '-p', '-M', '--cached', 'HEAD'] + f)
107 def doUpdateCache(filename):
108 runProgram(['git-update-cache', '--remove', '--add', '--replace', filename])
110 def doCommit(filesToKeep, fileRealNames, msg):
111 for file in filesToKeep:
112 # If we have a new file in the cache which we do not want to
113 # commit we have to remove it from the cache. We will add this
114 # cache entry back in to the cache at the end of this
115 # function.
116 if file.change == 'A':
117 runProgram(['git-update-cache', '--force-remove',
118 '--', file.srcName])
119 elif file.change == 'R':
120 runProgram(['git-update-cache', '--force-remove',
121 '--', file.dstName])
122 runProgram(['git-update-cache', '--add', '--replace',
123 '--cacheinfo', file.srcMode, file.srcSHA, file.srcName])
124 else:
125 runProgram(['git-update-cache', '--add', '--replace',
126 '--cacheinfo', file.srcMode, file.srcSHA, file.srcName])
128 tree = runProgram(['git-write-tree'])
129 tree = tree.rstrip()
131 if commitIsMerge():
132 merge = ['-p', 'MERGE_HEAD']
133 else:
134 merge = []
135 commit = runProgram(['git-commit-tree', tree, '-p', 'HEAD'] + merge, msg)
137 try:
138 f = open(os.environ['GIT_DIR'] + '/HEAD', 'w+')
139 f.write(commit)
140 f.close()
141 except OSError, e:
142 raise CommitError('write to ' + os.environ['GIT_DIR'] + '/HEAD', e.strerror)
144 try:
145 os.unlink(os.environ['GIT_DIR'] + '/MERGE_HEAD')
146 except OSError:
147 pass
149 for file in filesToKeep:
150 # Don't add files that are going to be deleted back to the cache
151 if file.change != 'D':
152 runProgram(['git-update-cache', '--add', '--replace', '--cacheinfo',
153 file.dstMode, file.dstSHA, file.dstName])
155 if file.change == 'R':
156 runProgram(['git-update-cache', '--remove', '--', file.srcName])
158 def discardFile(file):
159 runProgram(['git-read-tree', 'HEAD'])
160 c = file.change
161 if c == 'M' or c == 'T':
162 runProgram(['git-checkout-cache', '-f', '-q', '--', file.dstName])
163 elif c == 'A' or c == 'C':
164 # The file won't be tracked by git now. We could unlink it
165 # from the working directory, but that seems a little bit
166 # too dangerous.
167 pass
168 elif c == 'D':
169 runProgram(['git-checkout-cache', '-f', '-q', '--', file.dstName])
170 elif c == 'R':
171 # Same comment applies here as to the 'A' or 'C' case.
172 runProgram(['git-checkout-cache', '-f', '-q', '--', file.srcName])
174 def ignoreFile(file):
175 # Not yet implemented
176 pass
178 def commitIsMerge():
179 try:
180 os.stat(os.environ['GIT_DIR'] + '/MERGE_HEAD')
181 return True
182 except OSError:
183 return False
185 def mergeMessage():
186 return '''This is a merge commit if you do not want to commit a ''' + \
187 '''merge remove the file $GIT_DIR/MERGE_HEAD.'''