Add instructions for installing hct.py in the python module directory.
[hgct.git] / git.py
blob8083b9f4f01b26a1b0b1fbc7177b11a86d1e5040
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 initialize():
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'])):
30 print "Git archive not found."
31 print "Make sure that the current working directory contains a '.git' directory, or\nthat GIT_DIR is set appropriately."
32 sys.exit(1)
34 files = runProgram(['git-diff-files', '--name-only', '-z']).split('\0')
35 files.pop()
36 runXargsStyle(['git-update-index', '--remove', '--'], files)
38 parseDiffRE = re.compile(':([0-9]+) ([0-9]+) ([0-9a-f]{40}) ([0-9a-f]{40}) ([MCRNADUT])([0-9]*)')
39 def parseDiff(prog):
40 inp = runProgram(prog)
41 ret = []
42 try:
43 recs = inp.split("\0")
44 recs.pop() # remove last entry (which is '')
45 it = recs.__iter__()
46 while True:
47 rec = it.next()
48 m = parseDiffRE.match(rec)
50 if not m:
51 print "Unknown output from " + str(prog) + "!: " + rec + "\n"
52 continue
54 f = File()
55 f.srcMode = m.group(1)
56 f.dstMode = m.group(2)
57 f.srcSHA = m.group(3)
58 f.dstSHA = m.group(4)
59 if m.group(5) == 'N':
60 f.change = 'A'
61 else:
62 f.change = m.group(5)
63 f.score = m.group(6)
64 f.srcName = f.dstName = it.next()
66 if f.change == 'C' or f.change == 'R':
67 f.dstName = it.next()
68 f.patch = getPatch(f.srcName, f.dstName)
69 else:
70 f.patch = getPatch(f.srcName)
72 ret.append(f)
73 except StopIteration:
74 pass
75 return ret
77 def runXargsStyle(origProg, args):
78 steps = range(10, len(args), 10)
79 prog = origProg[:]
80 prev = 0
81 for i in steps:
82 prog.extend(args[prev:i])
83 runProgram(prog)
84 prog = origProg[:]
85 prev = i
87 prog.extend(args[prev:])
88 runProgram(prog)
90 def getUnknownFiles():
91 args = []
93 if settings().gitExcludeFile():
94 if os.path.exists(settings().gitExcludeFile()):
95 args.append('--exclude-from=' + settings().gitExcludeFile())
96 if settings().gitExcludeDir():
97 args.append('--exclude-per-directory=' + settings().gitExcludeDir())
99 inp = runProgram(['git-ls-files', '-z', '--others'] + args)
100 files = inp.split("\0")
101 files.pop() # remove last entry (which is '')
103 fileObjects = []
105 runXargsStyle(['git-update-index', '--add', '--'], files)
106 for fileName in files:
107 f = File()
108 f.srcName = f.dstName = fileName
109 f.change = '?'
110 f.patch = runProgram(['git-diff-cache', '-p', '--cached', 'HEAD', '--', fileName])
111 fileObjects.append(f)
112 f.text = 'New file: ' + fileName
114 runXargsStyle(['git-update-index', '--force-remove', '--'], files)
115 return fileObjects
117 # HEAD is src in the returned File objects. That is, srcName is the
118 # name in HEAD and dstName is the name in the cache.
119 def getFiles():
120 files = parseDiff('git-diff-files -z')
121 for f in files:
122 doUpdateCache(f.srcName)
124 files = parseDiff('git-diff-cache -z -M --cached HEAD')
125 for f in files:
126 c = f.change
127 if c == 'C':
128 f.text = 'Copy from ' + f.srcName + ' to ' + f.dstName
129 elif c == 'R':
130 f.text = 'Rename from ' + f.srcName + ' to ' + f.dstName
131 elif c == 'A':
132 f.text = 'New file: ' + f.srcName
133 elif c == 'D':
134 f.text = 'Deleted file: ' + f.srcName
135 elif c == 'T':
136 f.text = 'Type change: ' + f.srcName
137 else:
138 f.text = f.srcName
140 if settings().showUnknown:
141 files.extend(getUnknownFiles())
143 return files
145 def getPatch(file, otherFile = None):
146 if otherFile:
147 f = [file, otherFile]
148 else:
149 f = [file]
150 return runProgram(['git-diff-cache', '-p', '-M', '--cached', 'HEAD'] + f)
152 def doUpdateCache(filename):
153 runProgram(['git-update-index', '--remove', '--add', '--replace', '--', filename])
155 def doCommit(filesToKeep, filesToCommit, msg):
156 for file in filesToKeep:
157 # If we have a new file in the cache which we do not want to
158 # commit we have to remove it from the cache. We will add this
159 # cache entry back in to the cache at the end of this
160 # function.
161 if file.change == 'A':
162 runProgram(['git-update-index', '--force-remove',
163 '--', file.srcName])
164 elif file.change == 'R':
165 runProgram(['git-update-index', '--force-remove',
166 '--', file.dstName])
167 runProgram(['git-update-index', '--add', '--replace',
168 '--cacheinfo', file.srcMode, file.srcSHA, file.srcName])
169 elif file.change == '?':
170 pass
171 else:
172 runProgram(['git-update-index', '--add', '--replace',
173 '--cacheinfo', file.srcMode, file.srcSHA, file.srcName])
175 for file in filesToCommit:
176 if file.change == '?':
177 runProgram(['git-update-index', '--add', '--', file.dstName])
179 tree = runProgram(['git-write-tree'])
180 tree = tree.rstrip()
182 if commitIsMerge():
183 merge = ['-p', 'MERGE_HEAD']
184 else:
185 merge = []
186 commit = runProgram(['git-commit-tree', tree, '-p', 'HEAD'] + merge, msg).rstrip()
188 runProgram(['git-update-ref', 'HEAD', commit])
190 try:
191 os.unlink(os.environ['GIT_DIR'] + '/MERGE_HEAD')
192 except OSError:
193 pass
195 for file in filesToKeep:
196 # Don't add files that are going to be deleted back to the cache
197 if file.change != 'D' and file.change != '?':
198 runProgram(['git-update-index', '--add', '--replace', '--cacheinfo',
199 file.dstMode, file.dstSHA, file.dstName])
201 if file.change == 'R':
202 runProgram(['git-update-index', '--remove', '--', file.srcName])
204 def discardFile(file):
205 runProgram(['git-read-tree', 'HEAD'])
206 c = file.change
207 if c == 'M' or c == 'T':
208 runProgram(['git-checkout-cache', '-f', '-q', '--', file.dstName])
209 elif c == 'A' or c == 'C':
210 # The file won't be tracked by git now. We could unlink it
211 # from the working directory, but that seems a little bit
212 # too dangerous.
213 pass
214 elif c == 'D':
215 runProgram(['git-checkout-cache', '-f', '-q', '--', file.dstName])
216 elif c == 'R':
217 # Same comment applies here as to the 'A' or 'C' case.
218 runProgram(['git-checkout-cache', '-f', '-q', '--', file.srcName])
220 def ignoreFile(file):
221 ignoreExpr = re.sub(r'([][*?!\\])', r'\\\1', file.dstName)
223 excludefile = settings().gitExcludeFile()
224 excludefiledir = os.path.dirname(excludefile)
225 if not os.path.exists(excludefiledir):
226 os.mkdir(excludefiledir)
227 if not os.path.isdir(excludefiledir):
228 return
229 exclude = open(excludefile, 'a')
230 print >> exclude, ignoreExpr
231 exclude.close()
233 pass
235 def commitIsMerge():
236 try:
237 os.stat(os.environ['GIT_DIR'] + '/MERGE_HEAD')
238 return True
239 except OSError:
240 return False
242 def mergeMessage():
243 return '''This is a merge commit if you do not want to commit a ''' + \
244 '''merge remove the file $GIT_DIR/MERGE_HEAD.'''