Example #1
0
	def _loadDates(self):
		revision = 'HEAD' if self.getRef() == None else self.getRef()
		cmd = ('git log'
			+ ' --topo-order'
			+ ' --reverse'
			+ ' --pretty=format:"%H %at %an %ae"'
			+ ' ' + revision
			+ ' -- ' + config.ISSUES_DIR + "/" + self.getId())
		commits = getCmd(cmd)
		if commits:
			commits = commits.split('\n')
			self._createdDate = 		commits[0].split()[1]
			self._createdAuthorName = 	commits[0].split()[2]
			self._createdAuthorEmail = 	commits[0].split()[3]

			self._modifiedDate = 		commits[-1].split()[1]
			self._modifiedAuthorName = 	commits[-1].split()[2]
			self._modifiedAuthorEmail = commits[-1].split()[3]
		else:
			self._createdDate = 		0
			self._createdAuthorName = 	getCmd("git config user.name")
			self._createdAuthorEmail = 	getCmd("git config user.email")

			self._modifiedDate = 		0
			self._modifiedAuthorName = 	getCmd("git config user.name")
			self._modifiedAuthorEmail = getCmd("git config user.email")
Example #2
0
def prependGhiCommitMsg(commitMsgFilepath):
	"""Examines the git index that is about to be committed and 
	adds auto-generated commit message lines into the git commit
	message to represent any ghi related changes."""
	
	relIssuesPath = config.ISSUES_DIR[len(config.GIT_ROOT) + 1:] # +1 to remove '/'
	relGroupsPath = config.GROUPS_DIR[len(config.GIT_ROOT) + 1:] # +1 to remove '/'

	# Find any .ghi files in the index
	ghiMods = getCmd('git diff --cached --name-status -- '	+ config.GHI_DIR)
	
	if ghiMods == None:
		# Nothing of interest in index... bail
		return None
	
	# Build a set of suggested commit message lines based on the
	# changes to .ghi files in the index
	commitMsg=['\n']
	for mod in ghiMods.split('\n'):
		m = mod.split()

		# if file was an issue
		if m[1].count(relIssuesPath):
			
			issueID = m[1][len(relIssuesPath)+1:]

			# if issue was added
			if m[0] == 'A':
				commitMsg.extend(["# " + buildGhiAddMsg(issueID) + "\n"])
			# if issue was modified
			elif m[0] == 'M':
				commitMsg.extend(["# " + buildGhiEditMsg(issueID) + "\n"])
			# if issue was deleted
			elif m[0] == 'D':
				commitMsg.extend(["# " + buildGhiRmMsg(issueID) + "\n"])
				
		elif m[1].count(relGroupsPath):
			
			groupname = m[1][len(relGroupsPath)+1:]
			
			# Run through the group file and see exactly what was done.
			diff = getCmd("git diff --cached -- " + m[1]).split('\n')
			for line in diff:
				# Issue added
				if line[0] == '+' and not line[0:3] == '+++':
					commitMsg.extend(["# " + 
							buildGhiGroupAddMsg(groupname, line[1:]) + "\n"])
				# Issue removed
				if line[0] == '-' and not line[0:3] == '---':
					commitMsg.extend(["# " + 
							buildGhiGroupRmMsg(groupname, line[1:]) + "\n"])
			
	# Add the existing commit message (usually a default git template)
	with open(commitMsgFilepath, 'rb') as f:
		commitMsg.extend(f.readlines())
	
	# Now write out the full message
	with open(commitMsgFilepath, 'wb') as f:
		for line in commitMsg:
			f.write(line)
Example #3
0
def execute(args):
	
	# First validate arguments
	issueID = identifiers.getFullIssueIdFromLeadingSubstr(args.id)
	if issueID == None:
		# ID is required... no good
		print "Could not find issue: " + args.id
		return None

	# Load the existing issue
	issue = IssueFile.readIssueFromDisk(
							config.ISSUES_DIR + "/" + issueID);
	
	# Are we going to use interactive editing?	
	if args.status == None and args.title == None and args.description == None:
		tmpFile = config.GHI_DIR + "/" + "ISSUE_EDIT";
		IssueFile.writeEditableIssueToDisk(tmpFile, issue)
		tmpFileHash = getCmd("git hash-object " + tmpFile)

		subprocess.call([config.GIT_EDITOR, tmpFile])
		issue = IssueFile.readEditableIssueFromDisk(tmpFile)
		
		# Check to see if the tmpFile is unchanged
		if (tmpFileHash == getCmd("git hash-object " + tmpFile)):
			print "No change in Issue data. Issue not updated"
			return None
	
	# Set the status
	if args.status:
		# There is a potential bug here in situations where there is 
		# more than one status with the same value name
		statusUpdate = None
		for k,v in config.STATUS_OPTS.iteritems():
			if args.status == v:
				statusUpdate = k
				break
		
		if statusUpdate != None:
			issue.setStatus(statusUpdate)
		else:
			print "Status does not exist!"
			return None
	
	# Set title
	if args.title:
		issue.setTitle(args.title)
	
	# Set description
	if args.description:
		issue.setDescription(args.description)
	
	# Make changes to index for commit
	issuepath = config.ISSUES_DIR + "/" + issueID
	IssueFile.writeIssueToDisk(issuepath, issue)
	commit_helper.addToIndex(issuepath)
	
	if args.commit:
		commit_helper.commit()
Example #4
0
def cleanWcAndCommitGhiDir(msg):
	'''Cleans the git working copy and creates a git commit for the
	whole .ghi directory'''
	
	_stashWc()
	
	getCmd("git add " + config.GHI_DIR)
	
	cmdName = _getCallerModuleName()
	print getCmd('git commit -m "ghi-' + cmdName + ' ' + msg + '"')
	
	_restoreWc()
Example #5
0
def execute(args):
	issue = None
	
	# First validate arguments
	if (args.title == None and args.description == None):
		# If no arguments, drop into interactive mode
		tmpFile = config.GHI_DIR + "/" + "ISSUE_EDIT";
		issue = IssueProto()
		IssueFile.writeEditableIssueToDisk(tmpFile, issue)
		tmpFileHash = getCmd("git hash-object " + tmpFile)
		
		subprocess.call([config.GIT_EDITOR, tmpFile])
		issue = IssueFile.readEditableIssueFromDisk(tmpFile)
		
		# Check to see if the tmpFile is unchanged
		if (tmpFileHash == getCmd("git hash-object " + tmpFile)):
			print "Not enough data to create issue. No issue created."
			return None
	
	elif (args.title == None):
		# Title is required... no good
		print "An issue title is required. Try 'add' with no arguments for interactive mode"
		return None
	
	else:
		# Create new issue
		issue = IssueProto();
		
		# Set title
		issue.setTitle(args.title)
		
		# Set description
		if (args.description):
			issue.setDescription(args.description)

	if (issue):
		# Generate an issue ID
		issueID = str(identifiers.genNewIssueID())
		
		# Make changes to index for commit
		issuepath = config.ISSUES_DIR + "/" + issueID
		IssueFile.writeIssueToDisk(issuepath, issue)
		commit_helper.addToIndex(issuepath)
		
		if args.group:
			Group(args.group).addIssue(issueID)
			commit_helper.addToIndex(config.GROUPS_DIR + '/"' + args.group + '"')
		
		# Display the new issue ID to the user
		print issueID
		
		if args.commit:
			commit_helper.commit()
Example #6
0
	def _loadIssueFile(self):
		if self._ref == None:
			tmp = IssueFile.readIssueFromDisk(
				config.ISSUES_DIR + '/' + self._id)
		else:
			getCmd('git show ' + self.getRef() + ':' + config.mkPathRel(config.ISSUES_DIR) + '/' + self._id 
					+ ' > ' + config.mkPathRel(config.GHI_DIR) + '/tmpfile')
			tmp = IssueFile.readIssueFromDisk(config.GHI_DIR + '/tmpfile')
			getCmd('rm ' + config.GHI_DIR + '/tmpfile')
		
		self.setTitle(tmp.getTitle())
		self.setStatus(tmp.getStatus())
		self.setDescription(tmp.getDescription())
Example #7
0
def getGroupsForIssueId(issueID):
    groupPaths = getCmd("git grep --name-only " 
                + issueID + " -- " + config.GROUPS_DIR)

    groups = []
    pathPrefix = config.GROUPS_DIR[len(config.GIT_ROOT) + 1:] # +1 to remove '/'
    if groupPaths != None:
        for path in groupPaths.splitlines():
            groupname = path[len(pathPrefix) + 1:] # +1 to remove '/'
            groups.extend([groupname]) 
    
    return groups
Example #8
0
def execute(args):
    issueIDs = _getFilteredListofIssueIDs(args)
    if issueIDs == None:
        print "Could not find issue: " + args.id
        return
    
    elif len(issueIDs) == 1:
        print IssueDisplayBuilder(issueIDs[0]).getFullIssueDisplay()
            
    else:
        columns = [display.COLUMNS['id'],
                   display.COLUMNS['status'],
                   display.COLUMNS['groups'],
                   display.COLUMNS['title'],
                   display.COLUMNS['mdate']]
        
        # We may have a lot of issues in the list that would make the output
        # run pretty long, therefore page it.
        PageOutputBeyondThisPoint()
        
        header = '  '.join([truncateOrPadStrToWidth(col.name, col.length) for col in columns])
        print header
        print '-' * len(header)        

        # Any sorting required?
        if args.sort != None:
            issueIDs = _sortIssues(issueIDs, args.sort)
            
        # Check to see if a default issue sort has been configured for this repository
        else:
            sort = getCmd('git config issue.ls.sort')
            if sort != None:
                issueIDs = _sortIssues(issueIDs, sort)

        # Group arg can be passed as parameter or via configured default
        if args.group or getCmd('git config issue.ls.group') == 'true':
            _displayGrouped(issueIDs, columns)        
        else:
            _displayUnGrouped(issueIDs, columns)
Example #9
0
def _SelectPager():
	try:
		return os.environ['GIT_PAGER']
	except KeyError:
		pass

	pager = getCmd('git config core.pager')
	if pager:
		return pager

	try:
		return os.environ['PAGER']
	except KeyError:
		pass

	return 'less'
Example #10
0
def execute(args):
	if (args.id):
		issueID = getFullIssueIdFromLeadingSubstr(args.id)
		if issueID == None:
			print "Could not find issue: " + args.id
			return None
		
		# See if we can remove this issue at all without a --force
		issuePath = getPathFromId(issueID)
		if not args.force:
			issueStatus = getCmd("git status --porcelain -- " + issuePath)
			if issueStatus and issueStatus[0] =='A':
				print "Cannot remove issue without --force"
				return None
		
		# Remove the issue from any groups that contained it
		groupnames = group.getGroupsForIssueId(issueID)
		
		# If we're not forcing the remove, then we need to double-check
		# to make sure that we can actually remove the issue from each
		# group without breaking things... this seems like hack...
		# Why should we be having to check first before we execute later?
		# Should we just perform the change on the group objects and then
		# commit them?... maybe I'm missing something and this isn't a big deal.
		for name in groupnames:
			if not Group(name)._canRmIssueFromGroup(issueID,args.force):
				# Can't perform this operation without a force!
				print "Cannot remove issue from group '" + group + "' without --force"
				return None
				
		# All clear to remove the issue!... groups first if you please...
		for name in groupnames:
			Group(name).rmIssue(issueID, args.force) 
			
			# HACK HACK HACK
			# Should be executing a git command here to add the
			# subsequent group changes to the index, but I'm taking
			# a shortcut for the moment
		
		issueTitle = Issue(issueID).getTitle()
		
		# Remove the issue
		commit_helper.remove(issuePath, args.force)
		
		if args.commit:
			commit_helper.commit()
Example #11
0
	def STATUS_OPTS(self):
		'''List of legal issue status values'''
		from subprocess_helper import getCmd
		if not hasattr(self, '_STATUS_OPTS'):
			# Set the default status options to be used in case no
			# config file is present (shouldn't happen, but whatevs)
			self._STATUS_OPTS = {0:'New',1:'In progress',2:'Fixed'}

			status_options = getCmd('git config '
									+ '-f ' + self.GHI_DIR + '/config '
									+ '--get-regexp status.s')

			if status_options != None:
				for status in status_options.split('\n'):
					key,sep,val = str(status).partition(' ')
					self._STATUS_OPTS[int(key.lstrip('status.s'))] = val
		
		return self._STATUS_OPTS
Example #12
0
def _sortIssues(issueIDs, sortBy):
    if sortBy == None:
        return None
    
    if sortBy == 'id':
        # We don't need to do anything here. Since the issues are stored with the id as the
        # filename, then they will be automatically sorted.
        return issueIDs
    
    issuesPathPrefix = config.ISSUES_DIR[len(config.GIT_ROOT) + 1:] # +1 to remove '/'
    issueSortTuple =[]
    
    if sortBy == 'title':
        for issueID in issueIDs:
            issueSortTuple.extend([[Issue(issueID).getTitle(), getFullIssueIdFromLeadingSubstr(issueID)]])
    
    elif sortBy == 'status':
        # Organize the issues into status groups
        for sk in config.STATUS_OPTS:
            issues = getCmd('git grep -n ^' + str(sk) + '$ -- ' + config.ISSUES_DIR)
            if issues != None:
                for i in issues.splitlines():
                    issueSortTuple.extend([[sk, i.split(':')[0][len(issuesPathPrefix) + 1:]]]) # +1 to remove '/'
    
    elif sortBy == 'date' or sortBy == 'cdate':
        for issueID in issueIDs:
            issueSortTuple.extend([[Issue(issueID).getCreatedDate(), getFullIssueIdFromLeadingSubstr(issueID)]])
            
    elif sortBy == 'mdate':
        for issueID in issueIDs:
            issueSortTuple.extend([[Issue(issueID).getModifiedDate(), getFullIssueIdFromLeadingSubstr(issueID)]])
            
    # Sort by Date
    issueSortTuple.sort(key=lambda issue: issue[0])
    
    if len(issueSortTuple) > 0:
        return map (lambda issueID: issueID[1], issueSortTuple)
    
    return None
Example #13
0
 def _canRmIssueFromGroup(self, issueID, force = False):
     if force:
         return True
     
     groupIDs = self.getIssueIds()
     
     if groupIDs.count(issueID) == 0 or len(groupIDs) == 0:
         return False
     
     # If there is more than one issue in the group we can always
     # successfully remove (assuming the issue is in this group)
     if len(groupIDs) > 1:
         return True
     
     # If this group only has one issue and we're trying to remove
     # it then we have to remove the group file as well. If this is
     # the case and the group is already modified in the git
     # index then we need a force to make this happen
     else: # len(groupIDs) == 1
         groupGitStatus = getCmd('git status --porcelain -- "' + self.getPath() + '"') 
         if groupGitStatus and groupGitStatus[0] != " ":
             return False
     
     return True
Example #14
0
 def rmIssue(self, issueID, force = False):
     if not self._canRmIssueFromGroup(issueID, force):
         return False
     
     groupIDs = self.getIssueIds()
     
     # If we're removing the last issue in a file, then rm the file
     if len(groupIDs) == 1 and groupIDs[0] == issueID:
         # HACK HACK HACK
         # Should not be executing a git command here
         if force:
             getCmd('git rm -f "' + self.getPath() + '"')
         else:
             getCmd('git rm "' + self.getPath() + '"')
     else:
         with open(self.getPath(), "wb") as f:
             for identifier in groupIDs:
                 if not identifier == issueID:
                     f.write(identifier + "\n")
 
         # HACK HACK HACK
         # Should not be executing a git command here
         getCmd('git add "' + self.getPath() + '"')
Example #15
0
def execute(args):
	bFileAdd = False
		
	# Check to see if the .ghi directories have already been created
	# If it doesn't exist, create it.
	if os.path.isdir(config.GHI_DIR) == False:
		os.makedirs(config.GHI_DIR)
		_writeGhiDirGitIgnoreFile(config.GHI_DIR + "/.gitignore")
		bFileAdd = True

	if os.path.isdir(config.ISSUES_DIR) == False:
		os.makedirs(config.ISSUES_DIR)
		# Touch a .gitignore file in the ISSUES_DIR so that we can
		# track the directory in git before any issues get added
		getCmd("touch " + config.ISSUES_DIR + "/.gitignore")
		bFileAdd = True

	if os.path.isdir(config.GROUPS_DIR) == False:
		os.makedirs(config.GROUPS_DIR)
		# Touch a .gitignore file in the GROUPS_DIR so that we can
		# track the directory in git before any issues get added
		getCmd("touch " + config.GROUPS_DIR + "/.gitignore")
		bFileAdd = True
	
	for key, val in config.STATUS_OPTS.iteritems():
		getCmd('git config '
				+ '-f ' + config.GHI_DIR + '/config '
				+ 'status.s' + str(key) + ' "' + val + '"')
	
	if bFileAdd:
		commit_helper.cleanWcAndCommitGhiDir("Initializing ghi")

	# Alias "git issue" to ghi
	if args.ghi_path:
		getCmd("git config alias.issue '!" + args.ghi_path + "/ghi'")
		ghicmdpath = args.ghi_path
	else:
		getCmd("git config alias.issue '!ghi'")
		ghicmdpath = "ghi"
		
	# Insert git hooks
	getCmd("cp " + ghicmdpath + "/hooks/prepare-commit-msg "
				+ config.GIT_ROOT + "/.git/hooks/")
		
	# Clever successful response message... in the future it would
	# be nice if running 'ghi-init' on a git that already has issues
	# would give you a summary of stats on the current issues of that
	# git
	print "Initialized ghi, but this git currently has no issues"
Example #16
0
def execute(args):
	# Are we deleting something?
	if args.d or args.D:
		
		# see if we're deleting an existing issue from a group
		issueToDelete = args.d if args.d else args.D
		issueID = identifiers.getFullIssueIdFromLeadingSubstr(issueToDelete)
		if issueID:
			force = False if args.d else True
			
			# If no groupname is given, then we will remove from all groups
			# ... notice the hack here where args.id is holding the groupname
			# due to the currently lame and hacky argparsing
			if not args.id:
				# Remove the issue from any groups that contained it
				groupnames = group.getGroupsForIssueId(issueID)
				
				if len(groupnames) == 0:
					print "No groups to delete issue from!"
					return None
				
				# If we're not forcing the remove, then we need to double-check
				# to make sure that we can actually remove the issue from each
				# group without breaking things
				for name in groupnames:
					if not Group(name)._canRmIssue(issueID,force):
						# Can't perform this operation without a force!
						print "Cannot delete issue from group '" + name + "' without force option, '-D'"
						return None
				
				# All clear to remove the issue!... groups first if you please...
				for name in groupnames: 
					Group(name).rmIssue(issueID,force)
					# HACK HACK HACK
					# Should be executing a git command here to add the
					# subsequent group changes to the index, but I'm taking
					# a shortcut for the moment

				return None
			
			# HACK HACK HACK
			# The command line parsing here is totally messed up and so
			# rather than using the groupname we have to pretend here
			# that the id is the groupname... the command line just
			# needs to be rewritten :(
			Group(args.id).rmIssue(issueID, force)
			
			# HACK HACK HACK
			# Should be executing a git command here to add the
			# subsequent group changes to the index, but I'm taking
			# a shortcut for the moment
			return None
		
		# see if we're deleting a group entirely
		if group.exists(args.d):
			print "groupname = " + args.d
			getCmd('git rm "' + Group(args.d).getPath() + '"')
			return None
		elif group.exists(args.D):
			print "groupname = " + args.D
			getCmd('git rm -f "' + Group(args.D).getPath() + '"')
			return None
		
		# tried to delete, but we couldn't figure out what...
		groupname = args.d if args.d else args.D
		print "Could not delete '" + groupname  + "' without force option, '-D'"
		return None
	
	if args.groupname == None and args.id == None:
		print "\n".join(group.getListOfAllGroups())
		return None
	
	if args.groupname == None:
		# We don't support this syntax yet
		print "Command not currently supported"
		return None
	
	# get the full issue ID & Add the issue to the group
	issueID = identifiers.getFullIssueIdFromLeadingSubstr(args.id)
	Group(args.groupname).addIssue(issueID)
	commit_helper.addToIndex('"' + Group(args.groupname).getPath() + '"')
	
	if args.commit:
		commit_helper.commit()
Example #17
0
	def GIT_EDITOR(self):
		'''Get the root directory for all ghi files'''
		from subprocess_helper import getCmd
		if not hasattr(self, '_GIT_EDITOR'):
			self._GIT_EDITOR = getCmd('git config core.editor')
		return self._GIT_EDITOR
Example #18
0
def remove(path, force=False):
	if force:
		getCmd("git rm -f " + path)
	else:
		getCmd("git rm " + path)
Example #19
0
def commit(msg = None):
	if msg:
		getCmd('git commit -m "' + msg + '"')
	else:
		runCmd('git commit')
Example #20
0
def _restoreWc():
	# Now restore the working copy
	getCmd("git stash pop")
Example #21
0
def _stashWc():
	# Adding an issue includes adding a new commit to the repository.
	# To make sure that our commit doesn't include anything other
	# than this issue, clean the working copy using git-stash
	getCmd("git stash --all")
Example #22
0
def addToIndex(path):
	getCmd("git add " + path)
Example #23
0
	def GIT_ROOT(self):
		'''Get the git top-level directory'''
		from subprocess_helper import getCmd
		if not hasattr(self, '_GIT_ROOT'):
			self._GIT_ROOT = getCmd('git rev-parse --show-toplevel')
		return self._GIT_ROOT