Clean-up string constant in about box
[hgct.git] / git.py
blob90e9771b2e71c4d5664cf89edc7e99d4db725ad7
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, itertools
17 from sets import Set
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 updateIndex(['--remove'], files)
38 class GitFile(File):
39 def __init__(self, unknown=False):
40 File.__init__(self)
41 self.unknown = unknown
43 def code(self):
44 '''Only defined for non-unknown files'''
45 assert(self.text != None)
46 return (self.text, self.dstSHA, self.dstMode)
48 def getPatchImpl(self):
49 if self.unknown:
50 updateIndex(['--add'], [self.srcName])
51 patch = runProgram(['git-diff-index', '-p', '--cached', 'HEAD', '--', self.srcName])
52 updateIndex(['--force-remove'], [self.srcName])
53 return patch
54 elif self.change == 'C' or self.change == 'R':
55 return getPatch(self.srcName, self.dstName)
56 else:
57 return getPatch(self.srcName)
59 class GitFileSet(FileSet):
60 def __init__(self, addCallback, removeCallback):
61 FileSet.__init__(self, addCallback, removeCallback)
62 self.codeDict = {}
64 def add(self, file):
65 if not file.unknown:
66 self.codeDict[file.code()] = file
67 FileSet.add(self, file)
69 def remove(self, file):
70 if not file.unknown:
71 del self.codeDict[file.code()]
72 FileSet.remove(self, file)
74 def getByCode(self, file):
75 if file.unknown:
76 return None
77 else:
78 return self.codeDict.get(file.code())
80 def fileSetFactory(addCallback, removeCallback):
81 return GitFileSet(addCallback, removeCallback)
83 parseDiffRE = re.compile(':([0-9]+) ([0-9]+) ([0-9a-f]{40}) ([0-9a-f]{40}) ([MCRNADUT])([0-9]*)')
84 def parseDiff(prog):
85 inp = runProgram(prog)
86 ret = []
87 try:
88 recs = inp.split("\0")
89 recs.pop() # remove last entry (which is '')
90 it = recs.__iter__()
91 while True:
92 rec = it.next()
93 m = parseDiffRE.match(rec)
95 if not m:
96 print "Unknown output from " + str(prog) + "!: " + rec + "\n"
97 continue
99 f = GitFile()
100 f.srcMode = m.group(1)
101 f.dstMode = m.group(2)
102 f.srcSHA = m.group(3)
103 f.dstSHA = m.group(4)
104 if m.group(5) == 'N':
105 f.change = 'A'
106 else:
107 f.change = m.group(5)
108 f.score = m.group(6)
109 f.srcName = f.dstName = it.next()
111 if f.change == 'C' or f.change == 'R':
112 f.dstName = it.next()
114 ret.append(f)
115 except StopIteration:
116 pass
117 return ret
119 # origProg is a sequence of strings the first element is the program
120 # name and subsequent elements are arguments. args is a sequence of
121 # sequences. The function will repeatedly feed
123 # origProg.extend(flatten(args[i:j]))
125 # for some indices i and j to runProgram in such a way that every
126 # sequence in args is fed to runProgram exactly once.
127 def runXargsStyle(origProg, args):
128 for a in args:
129 assert(type(a) is list)
130 steps = range(10, len(args), 10)
131 prog = origProg[:]
132 prev = 0
133 for i in steps:
134 for a in args[prev:i]:
135 prog.extend(a)
136 runProgram(prog)
137 prog = origProg[:]
138 prev = i
140 for a in args[prev:]:
141 prog.extend(a)
142 runProgram(prog)
144 def updateIndex(args, fileNames):
145 # Make sure we don't get one single string as fileNames. As
146 # strings are sequences strange things happen in the call to
147 # join.
148 assert(type(fileNames) is list)
150 runProgram(['git-update-index'] + args + ['-z', '--stdin'], input='\0'.join(fileNames)+'\0')
152 def getUnknownFiles():
153 args = []
155 if settings().gitExcludeFile():
156 if os.path.exists(settings().gitExcludeFile()):
157 args.append('--exclude-from=' + settings().gitExcludeFile())
158 if settings().gitExcludeDir():
159 args.append('--exclude-per-directory=' + settings().gitExcludeDir())
161 inp = runProgram(['git-ls-files', '-z', '--others'] + args)
162 files = inp.split("\0")
163 files.pop() # remove last entry (which is '')
165 fileObjects = []
167 for fileName in files:
168 f = GitFile(unknown=True)
169 f.srcName = f.dstName = fileName
170 f.change = '?'
172 fileObjects.append(f)
173 f.text = 'New file: ' + fileName
175 return fileObjects
177 def getChangedFiles():
178 files = parseDiff('git-diff-index -z -M --cached HEAD')
179 for f in files:
180 c = f.change
181 if c == 'C':
182 f.text = 'Copy from ' + f.srcName + ' to ' + f.dstName
183 elif c == 'R':
184 f.text = 'Rename from ' + f.srcName + ' to ' + f.dstName
185 elif c == 'A':
186 f.text = 'New file: ' + f.srcName
187 elif c == 'D':
188 f.text = 'Deleted file: ' + f.srcName
189 elif c == 'T':
190 f.text = 'Type change: ' + f.srcName
191 else:
192 f.text = f.srcName
193 return files
195 # HEAD is src in the returned File objects. That is, srcName is the
196 # name in HEAD and dstName is the name in the cache.
197 def updateFiles(fileSet):
198 files = parseDiff('git-diff-files -z')
199 updateIndex(['--remove', '--add', '--replace'], [f.srcName for f in files])
201 markForDeletion = Set()
202 for f in fileSet:
203 markForDeletion.add(f)
205 if settings().showUnknown:
206 unknowns = getUnknownFiles()
207 else:
208 unknowns = []
210 files = getChangedFiles()
212 for f in itertools.chain(files, unknowns):
213 fs = fileSet.getByCode(f)
214 if fs:
215 markForDeletion.discard(fs)
216 else:
217 fileSet.add(f)
219 for f in markForDeletion:
220 fileSet.remove(f)
222 def getPatch(file, otherFile = None):
223 if otherFile:
224 f = [file, otherFile]
225 else:
226 f = [file]
227 return runProgram(['git-diff-index', '-p', '-M', '--cached', 'HEAD', '--'] + f)
229 def doCommit(filesToKeep, filesToCommit, msg):
230 # If we have a new file in the cache which we do not want to
231 # commit we have to remove it from the cache. We will add this
232 # cache entry back in to the cache at the end of this
233 # function.
234 updateIndex(['--force-remove'], [f.srcName for f in filesToKeep if f.change == 'A'])
236 updateIndex(['--force-remove'],
237 [f.dstName for f in filesToKeep if f.change == 'R'])
238 runXargsStyle(['git-update-index', '--add', '--replace'],
239 [['--cacheinfo', f.srcMode, f.srcSHA, f.srcName] \
240 for f in filesToKeep if f.change == 'R'])
242 runXargsStyle(['git-update-index', '--add', '--replace'],
243 [['--cacheinfo', f.srcMode, f.srcSHA, f.srcName] \
244 for f in filesToKeep if f.change != 'A' and \
245 f.change != 'R' and \
246 f.change != '?'])
248 updateIndex(['--add'], [f.dstName for f in filesToCommit if f.change == '?'])
250 tree = runProgram(['git-write-tree'])
251 tree = tree.rstrip()
253 if commitIsMerge():
254 merge = ['-p', 'MERGE_HEAD']
255 else:
256 merge = []
257 commit = runProgram(['git-commit-tree', tree, '-p', 'HEAD'] + merge, msg).rstrip()
259 runProgram(['git-update-ref', 'HEAD', commit])
261 try:
262 os.unlink(os.environ['GIT_DIR'] + '/MERGE_HEAD')
263 except OSError:
264 pass
266 # Don't add files that are going to be deleted back to the cache
267 runXargsStyle(['git-update-index', '--add', '--replace'],
268 [['--cacheinfo', f.dstMode, f.dstSHA, f.dstName] \
269 for f in filesToKeep if f.change != 'D' and \
270 f.change != '?'])
271 updateIndex(['--remove'], [f.srcName for f in filesToKeep if f.change == 'R'])
273 def discardFile(file):
274 runProgram(['git-read-tree', 'HEAD'])
275 c = file.change
276 if c == 'M' or c == 'T':
277 runProgram(['git-checkout-index', '-f', '-q', '--', file.dstName])
278 elif c == 'A' or c == 'C':
279 # The file won't be tracked by git now. We could unlink it
280 # from the working directory, but that seems a little bit
281 # too dangerous.
282 pass
283 elif c == 'D':
284 runProgram(['git-checkout-index', '-f', '-q', '--', file.dstName])
285 elif c == 'R':
286 # Same comment applies here as to the 'A' or 'C' case.
287 runProgram(['git-checkout-index', '-f', '-q', '--', file.srcName])
289 def ignoreFile(file):
290 ignoreExpr = re.sub(r'([][*?!\\])', r'\\\1', file.dstName)
292 excludefile = settings().gitExcludeFile()
293 excludefiledir = os.path.dirname(excludefile)
294 if not os.path.exists(excludefiledir):
295 os.mkdir(excludefiledir)
296 if not os.path.isdir(excludefiledir):
297 return
298 exclude = open(excludefile, 'a')
299 print >> exclude, ignoreExpr
300 exclude.close()
302 pass
304 def commitIsMerge():
305 try:
306 os.stat(os.environ['GIT_DIR'] + '/MERGE_HEAD')
307 return True
308 except OSError:
309 return False
311 def mergeMessage():
312 return '''This is a merge commit if you do not want to commit a ''' + \
313 '''merge remove the file $GIT_DIR/MERGE_HEAD.'''