# Bazaar VCS back-end code for qct
#
# Copyright 2006 Steve Borho
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
from qctlib.utils import runProgram
from tempfile import mkstemp
import os
class qctVcsBzr:
def initRepo(self, argv):
'''Initialize your revision control system, open repository'''
# Verify we have a valid repository
output = runProgram(['bzr', 'root'], expectedexits=[0,3])
words = output.split(' ')
if len(words) == 1:
self.repoRoot = output[:-len(os.linesep)]
return 0
else:
print output
return -1
def capabilities(self):
'''Return a list of optional capabilities supported by this VCS'''
# bazaar does not seem to support file copies or marking moves
# which were initially done outside of revision detection (using mv)
return ('ignore', 'progressbar')
def generateParentFile(self, workingFile):
'''The GUI needs this file's parent revision in place so the user
can select individual changes for commit (basically a revert)
'''
runProgram(['bzr', 'revert', '--no-backup', workingFile])
def addIgnoreMask(self, newIgnoreMask):
'''The user has provided a new ignoreMask to be added to revision control'''
runProgram(['bzr', 'ignore', newIgnoreMask])
print "Added '%s' to ignore mask" % newIgnoreMask
def getLogTemplate(self):
'''Request default log message template from VCS'''
logFileName = self.repoRoot + "/.commit.template"
try:
f = open(logFileName)
text = f.read()
f.close()
return text
except IOError:
return ''
def getAutoSelectTypes(self):
'''Return annotations of file types which should be automatically
selected when a new commit is started'''
return ['A', 'M', 'R', '>']
def dirtyCache(self, fileName):
'''The GUI would like us to forget cached data for this file'''
for itemName in self.diffCache.keys():
if itemName[2:] == fileName:
del self.diffCache[itemName]
def scanFiles(self, showIgnored, pb = None):
'''Request scan for all commitable files from VCS'''
# Called at startup, when 'Refresh' button is pressed, or when
# showIgnored toggled.
self.diffCache = { }
statusOutput = runProgram(['bzr', 'status'])
if pb: pb.setValue(1)
recs = statusOutput.split(os.linesep)
recs.pop() # remove last entry (which is '')
itemList = []
fileList = []
type = ''
for line in recs:
if line == 'added:': type = 'A'
elif line == 'modified:': type = 'M'
elif line == 'unknown:': type = '?'
elif line == 'removed:': type = 'R'
elif line == 'renamed:': type = '>'
elif line[-1] == '/':
line = '' # nop, just skipping this directory
else:
# Prevent files from showing up w/ two status
if line[2:] not in fileList:
itemList.append(type + " " + line[2:])
fileList.append(line[2:])
if pb: pb.setValue(2)
if showIgnored:
statusOutput = runProgram(['bzr', 'ls', '--ignored'])
recs = statusOutput.split(os.linesep)
recs.pop() # remove last entry (which is '')
for fileName in recs:
itemList.append("I " + fileName)
if pb: pb.setValue(3)
return itemList
def getFileStatus(self, itemName):
'''Request file deltas from VCS'''
annotation = itemName[0]
fileName = itemName[2:]
bFileName = "%" + fileName + ""
if annotation == 'A':
note = bFileName + " has been added to VCS, but has never been commited"
if self.diffCache.has_key(itemName):
text = self.diffCache[itemName]
else:
text = runProgram(['bzr', 'diff', fileName], expectedexits=[0,1])[:-len(os.linesep)]
self.diffCache[itemName] = text
return note + os.linesep + text
elif annotation == 'R':
note = bFileName + " has been marked for deletion, but has not yet been commited"
if self.diffCache.has_key(itemName):
text = self.diffCache[itemName]
else:
text = runProgram(['bzr', 'diff', fileName], expectedexits=[0,1])[:-len(os.linesep)]
self.diffCache[itemName] = text
return note + os.linesep + text
elif annotation == 'M':
note = bFileName + " has been modified in your working directory"
if self.diffCache.has_key(itemName):
text = self.diffCache[itemName]
else:
text = runProgram(['bzr', 'diff', fileName], expectedexits=[0,1])[:-len(os.linesep)]
self.diffCache[itemName] = text
return note + os.linesep + text
elif annotation == '?':
note = bFileName + " is not currently revisioned, will be added to VCS if commited"
if self.diffCache.has_key(itemName):
text = self.diffCache[itemName]
else:
text = runProgram(['diff', '-u', '/dev/null', fileName], expectedexits=[0,1,2])
if not text: text = ""
self.diffCache[itemName] = text
return note + os.linesep + text
elif annotation == 'I':
note = bFileName + " is usually ignored, but will be added to VCS if commited" + os.linesep
if self.diffCache.has_key(itemName):
text = self.diffCache[itemName]
else:
text = runProgram(['diff', '-u', '/dev/null', fileName], expectedexits=[0,1,2])
if not text: text = ""
self.diffCache[itemName] = text
return note + os.linesep + text
elif annotation == '>':
return "%Rename event: " + fileName
else:
return "Unknown file type " + annotation
def commitFiles(self, selectedFileList, logMsgText):
'''Commit selected files'''
# Files in list are annotated (A, M, etc) so this function can
# mark files for add or delete as necessary before instigating the commit.
commitFileNames = []
renameFiles = []
for f in selectedFileList:
annotation = f[0]
fileName = f[2:]
if annotation == '?' or annotation == 'I':
print "Adding %s to revision control" % fileName
runProgram(['bzr', 'add', fileName])
commitFileNames.append(fileName)
elif annotation == '>':
print "Commit rename: %s" % fileName
renameFiles += fileName.split(' => ')
else:
commitFileNames.append(fileName)
# Renamed files may be on the modified list as well, so we add
# them at the end to prevent duplicates
for f in renameFiles:
if f not in commitFileNames:
commitFileNames.append(f)
(fd, filename) = mkstemp()
file = os.fdopen(fd, "w+b")
file.write(logMsgText)
file.close()
runProgram(['bzr', 'commit', '-F', filename] + commitFileNames)
print "%d file(s) commited: %s" % (len(selectedFileList), ', '.join(commitFileNames))
def addFiles(self, selectedFileList):
'''Add selected files to version control'''
runProgram(['bzr', 'add'] + selectedFileList)
def revertFiles(self, selectedFileList):
'''Revert selected files to last revisioned state'''
revertFileNames = []
for f in selectedFileList:
annotation = f[0]
fileName = f[2:]
if annotation == 'R':
print "deleted %s recovered from revision control" % fileName
revertFileNames.append(fileName)
elif annotation == '>':
print "rename %s recovered from revision control" % fileName
renameFiles = fileName.split(' => ')
revertFileNames.append(renameFiles[0])
elif annotation == 'A':
print "added %s forgot from revision control" % fileName
revertFileNames.append(fileName)
elif annotation == 'M':
print "modifications to %s reverted" % fileName
revertFileNames.append(fileName)
else:
print "File %s not reverted" % fileName
if len(revertFileNames):
runProgram(['bzr', 'revert'] + revertFileNames)
print "%d file(s) reverted: %s" % (len(revertFileNames), ', '.join(revertFileNames))
else:
print "No revertable files"
# vim: tw=120