Staging
v0.5.1
https://github.com/git/git
Revision 883653babd8ee7ea23e6a5c392bb739348b1eb61 authored by Junio C Hamano on 13 September 2006, 06:53:27 UTC, committed by Junio C Hamano on 13 September 2006, 07:54:43 UTC
Fetch over http from a repository that uses alternates to borrow
from neighbouring repositories were quite broken, apparently for
some time now.

We parse input and count bytes to allocate the new buffer, and
when we copy into that buffer we know exactly how many bytes we
want to copy from where.  Using strlcpy for it was simply
stupid, and the code forgot to take it into account that strlcpy
terminated the string with NUL.

Signed-off-by: Junio C Hamano <junkio@cox.net>
1 parent 60a6bf5
Raw File
Tip revision: 883653babd8ee7ea23e6a5c392bb739348b1eb61 authored by Junio C Hamano on 13 September 2006, 06:53:27 UTC
http-fetch: fix alternates handling.
Tip revision: 883653b
gitMergeCommon.py
#
# Copyright (C) 2005 Fredrik Kuivinen
#

import sys, re, os, traceback
from sets import Set

def die(*args):
    printList(args, sys.stderr)
    sys.exit(2)

def printList(list, file=sys.stdout):
    for x in list:
        file.write(str(x))
        file.write(' ')
    file.write('\n')

import subprocess

# Debugging machinery
# -------------------

DEBUG = 0
functionsToDebug = Set()

def addDebug(func):
    if type(func) == str:
        functionsToDebug.add(func)
    else:
        functionsToDebug.add(func.func_name)

def debug(*args):
    if DEBUG:
        funcName = traceback.extract_stack()[-2][2]
        if funcName in functionsToDebug:
            printList(args)

# Program execution
# -----------------

class ProgramError(Exception):
    def __init__(self, progStr, error):
        self.progStr = progStr
        self.error = error

    def __str__(self):
        return self.progStr + ': ' + self.error

addDebug('runProgram')
def runProgram(prog, input=None, returnCode=False, env=None, pipeOutput=True):
    debug('runProgram prog:', str(prog), 'input:', str(input))
    if type(prog) is str:
        progStr = prog
    else:
        progStr = ' '.join(prog)
    
    try:
        if pipeOutput:
            stderr = subprocess.STDOUT
            stdout = subprocess.PIPE
        else:
            stderr = None
            stdout = None
        pop = subprocess.Popen(prog,
                               shell = type(prog) is str,
                               stderr=stderr,
                               stdout=stdout,
                               stdin=subprocess.PIPE,
                               env=env)
    except OSError, e:
        debug('strerror:', e.strerror)
        raise ProgramError(progStr, e.strerror)

    if input != None:
        pop.stdin.write(input)
    pop.stdin.close()

    if pipeOutput:
        out = pop.stdout.read()
    else:
        out = ''

    code = pop.wait()
    if returnCode:
        ret = [out, code]
    else:
        ret = out
    if code != 0 and not returnCode:
        debug('error output:', out)
        debug('prog:', prog)
        raise ProgramError(progStr, out)
#    debug('output:', out.replace('\0', '\n'))
    return ret

# Code for computing common ancestors
# -----------------------------------

currentId = 0
def getUniqueId():
    global currentId
    currentId += 1
    return currentId

# The 'virtual' commit objects have SHAs which are integers
shaRE = re.compile('^[0-9a-f]{40}$')
def isSha(obj):
    return (type(obj) is str and bool(shaRE.match(obj))) or \
           (type(obj) is int and obj >= 1)

class Commit(object):
    __slots__ = ['parents', 'firstLineMsg', 'children', '_tree', 'sha',
                 'virtual']

    def __init__(self, sha, parents, tree=None):
        self.parents = parents
        self.firstLineMsg = None
        self.children = []

        if tree:
            tree = tree.rstrip()
            assert(isSha(tree))
        self._tree = tree

        if not sha:
            self.sha = getUniqueId()
            self.virtual = True
            self.firstLineMsg = 'virtual commit'
            assert(isSha(tree))
        else:
            self.virtual = False
            self.sha = sha.rstrip()
        assert(isSha(self.sha))

    def tree(self):
        self.getInfo()
        assert(self._tree != None)
        return self._tree

    def shortInfo(self):
        self.getInfo()
        return str(self.sha) + ' ' + self.firstLineMsg

    def __str__(self):
        return self.shortInfo()

    def getInfo(self):
        if self.virtual or self.firstLineMsg != None:
            return
        else:
            info = runProgram(['git-cat-file', 'commit', self.sha])
            info = info.split('\n')
            msg = False
            for l in info:
                if msg:
                    self.firstLineMsg = l
                    break
                else:
                    if l.startswith('tree'):
                        self._tree = l[5:].rstrip()
                    elif l == '':
                        msg = True

class Graph:
    def __init__(self):
        self.commits = []
        self.shaMap = {}

    def addNode(self, node):
        assert(isinstance(node, Commit))
        self.shaMap[node.sha] = node
        self.commits.append(node)
        for p in node.parents:
            p.children.append(node)
        return node

    def reachableNodes(self, n1, n2):
        res = {}
        def traverse(n):
            res[n] = True
            for p in n.parents:
                traverse(p)

        traverse(n1)
        traverse(n2)
        return res

    def fixParents(self, node):
        for x in range(0, len(node.parents)):
            node.parents[x] = self.shaMap[node.parents[x]]

# addDebug('buildGraph')
def buildGraph(heads):
    debug('buildGraph heads:', heads)
    for h in heads:
        assert(isSha(h))

    g = Graph()

    out = runProgram(['git-rev-list', '--parents'] + heads)
    for l in out.split('\n'):
        if l == '':
            continue
        shas = l.split(' ')

        # This is a hack, we temporarily use the 'parents' attribute
        # to contain a list of SHA1:s. They are later replaced by proper
        # Commit objects.
        c = Commit(shas[0], shas[1:])

        g.commits.append(c)
        g.shaMap[c.sha] = c

    for c in g.commits:
        g.fixParents(c)

    for c in g.commits:
        for p in c.parents:
            p.children.append(c)
    return g

# Write the empty tree to the object database and return its SHA1
def writeEmptyTree():
    tmpIndex = os.environ.get('GIT_DIR', '.git') + '/merge-tmp-index'
    def delTmpIndex():
        try:
            os.unlink(tmpIndex)
        except OSError:
            pass
    delTmpIndex()
    newEnv = os.environ.copy()
    newEnv['GIT_INDEX_FILE'] = tmpIndex
    res = runProgram(['git-write-tree'], env=newEnv).rstrip()
    delTmpIndex()
    return res

def addCommonRoot(graph):
    roots = []
    for c in graph.commits:
        if len(c.parents) == 0:
            roots.append(c)

    superRoot = Commit(sha=None, parents=[], tree=writeEmptyTree())
    graph.addNode(superRoot)
    for r in roots:
        r.parents = [superRoot]
    superRoot.children = roots
    return superRoot

def getCommonAncestors(graph, commit1, commit2):
    '''Find the common ancestors for commit1 and commit2'''
    assert(isinstance(commit1, Commit) and isinstance(commit2, Commit))

    def traverse(start, set):
        stack = [start]
        while len(stack) > 0:
            el = stack.pop()
            set.add(el)
            for p in el.parents:
                if p not in set:
                    stack.append(p)
    h1Set = Set()
    h2Set = Set()
    traverse(commit1, h1Set)
    traverse(commit2, h2Set)
    shared = h1Set.intersection(h2Set)

    if len(shared) == 0:
        shared = [addCommonRoot(graph)]
        
    res = Set()

    for s in shared:
        if len([c for c in s.children if c in shared]) == 0:
            res.add(s)
    return list(res)
back to top