2017-12-18 193 views
1

我正在研究一个项目,其中我的项目集成了git。为了整合目的,我使用了go-git库。我的问题是如何以编程方式找到两个分支的共同祖先?我想实现这个功能。看起来像go-git和其他去库不提供这样的功能。如何以编程方式查找两个分支的共同祖先

+0

行为小程序https://github.com/src-d/ go-git/issues/679 – orirawlings

回答

0

下面是使用go-git模仿的git merge-base --all

package main 

import (
    "fmt" 
    "os" 

    "gopkg.in/src-d/go-git.v4" 
    . "gopkg.in/src-d/go-git.v4/_examples" 
    "gopkg.in/src-d/go-git.v4/plumbing" 
    "gopkg.in/src-d/go-git.v4/plumbing/object" 
    "gopkg.in/src-d/go-git.v4/plumbing/storer" 
) 

// store h in set, s, handling nil s if necessary. Return new set. 
func store(s map[plumbing.Hash]bool, h plumbing.Hash) map[plumbing.Hash]bool { 
    if s == nil { 
     s = make(map[plumbing.Hash]bool) 
    } 
    s[h] = true 
    return s 
} 

// mergeBase finds best common ancestors between two commits to use in a 
// three-way merge. One common ancestor is better than another common ancestor 
// if the latter is an ancestor of the former. A common ancestor that does not 
// have any better common ancestor is a best common ancestor, i.e. a merge base. 
// Note that there can be more than one merge base for a pair of commits. 
func mergeBase(s storer.EncodedObjectStorer, a, b plumbing.Hash) ([]plumbing.Hash, error) { 
    commitA, err := object.GetCommit(s, a) 
    if err != nil { 
     return nil, err 
    } 

    commitB, err := object.GetCommit(s, b) 
    if err != nil { 
     return nil, err 
    } 

    // Mapping of direct descendants of each commit we visit 
    desc := make(map[plumbing.Hash]map[plumbing.Hash]bool) 

    // Set of commits reachable from a 
    reachableFromA := make(map[plumbing.Hash]bool) 

    // Walk commits reachable from A 
    err = object.NewCommitPreorderIter(commitA, nil, nil).ForEach(func(c *object.Commit) error { 
     reachableFromA[c.Hash] = true 
     for _, h := range c.ParentHashes { 
      desc[h] = store(desc[h], c.Hash) 
     } 
     return nil 
    }) 
    if err != nil { 
     return nil, err 
    } 

    // Set of common commits between a and b 
    common := make(map[plumbing.Hash]bool) 

    // Walk commits reachable from B 
    err = object.NewCommitPreorderIter(commitB, nil, nil).ForEach(func(c *object.Commit) error { 
     if reachableFromA[c.Hash] { 
      common[c.Hash] = true 
     } 
     for _, h := range c.ParentHashes { 
      desc[h] = store(desc[h], c.Hash) 
     } 
     return nil 
    }) 
    if err != nil { 
     return nil, err 
    } 

    best := make(map[plumbing.Hash]bool) 

    // Trim down the set of common commits to only those that are best 
    for h := range common { 
     best[h] = true 
     for child := range desc[h] { 
      if common[child] { 
       // there is a descendant to h that is common to both a and b. h is not in best. 
       delete(best, h) 
       break 
      } 
     } 
    } 

    var result []plumbing.Hash 
    for h := range best { 
     result = append(result, h) 
    } 
    return result, nil 
} 

// Open an existing repository in a specific folder. 
func main() { 
    CheckArgs("<path> <commitA> <commitB>") 
    path := os.Args[1] 
    a := plumbing.NewHash(os.Args[2]) 
    b := plumbing.NewHash(os.Args[3]) 

    r, err := git.PlainOpen(path) 
    CheckIfError(err) 

    bases, err := mergeBase(r.Storer, a, b) 
    CheckIfError(err) 

    for _, b := range bases { 
     fmt.Println(b) 
    } 
} 
+0

谢谢@orirawlings –

1

你可以使用命令的结果:

git merge-base branch1 branch2 
+0

我想在golang中实现这个命令。 –

+0

也许看看https://github.com/libgit2/git2go – Philippe