diff options
Diffstat (limited to 'vendor/github.com/libgit2/git2go/merge.go')
-rw-r--r-- | vendor/github.com/libgit2/git2go/merge.go | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/vendor/github.com/libgit2/git2go/merge.go b/vendor/github.com/libgit2/git2go/merge.go new file mode 100644 index 000000000..adc521a2b --- /dev/null +++ b/vendor/github.com/libgit2/git2go/merge.go @@ -0,0 +1,441 @@ +package git + +/* +#include <git2.h> + +extern git_annotated_commit** _go_git_make_merge_head_array(size_t len); +extern void _go_git_annotated_commit_array_set(git_annotated_commit** array, git_annotated_commit* ptr, size_t n); +extern git_annotated_commit* _go_git_annotated_commit_array_get(git_annotated_commit** array, size_t n); +extern int _go_git_merge_file(git_merge_file_result*, char*, size_t, char*, unsigned int, char*, size_t, char*, unsigned int, char*, size_t, char*, unsigned int, git_merge_file_options*); + +*/ +import "C" +import ( + "reflect" + "runtime" + "unsafe" +) + +type AnnotatedCommit struct { + ptr *C.git_annotated_commit + r *Repository +} + +func newAnnotatedCommitFromC(ptr *C.git_annotated_commit, r *Repository) *AnnotatedCommit { + mh := &AnnotatedCommit{ptr: ptr, r: r} + runtime.SetFinalizer(mh, (*AnnotatedCommit).Free) + return mh +} + +func (mh *AnnotatedCommit) Free() { + runtime.SetFinalizer(mh, nil) + C.git_annotated_commit_free(mh.ptr) +} + +func (r *Repository) AnnotatedCommitFromFetchHead(branchName string, remoteURL string, oid *Oid) (*AnnotatedCommit, error) { + cbranchName := C.CString(branchName) + defer C.free(unsafe.Pointer(cbranchName)) + + cremoteURL := C.CString(remoteURL) + defer C.free(unsafe.Pointer(cremoteURL)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var ptr *C.git_annotated_commit + ret := C.git_annotated_commit_from_fetchhead(&ptr, r.ptr, cbranchName, cremoteURL, oid.toC()) + runtime.KeepAlive(oid) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newAnnotatedCommitFromC(ptr, r), nil +} + +func (r *Repository) LookupAnnotatedCommit(oid *Oid) (*AnnotatedCommit, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var ptr *C.git_annotated_commit + ret := C.git_annotated_commit_lookup(&ptr, r.ptr, oid.toC()) + runtime.KeepAlive(oid) + if ret < 0 { + return nil, MakeGitError(ret) + } + return newAnnotatedCommitFromC(ptr, r), nil +} + +func (r *Repository) AnnotatedCommitFromRef(ref *Reference) (*AnnotatedCommit, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var ptr *C.git_annotated_commit + ret := C.git_annotated_commit_from_ref(&ptr, r.ptr, ref.ptr) + runtime.KeepAlive(r) + runtime.KeepAlive(ref) + if ret < 0 { + return nil, MakeGitError(ret) + } + return newAnnotatedCommitFromC(ptr, r), nil +} + +type MergeTreeFlag int + +const ( + // Detect renames that occur between the common ancestor and the "ours" + // side or the common ancestor and the "theirs" side. This will enable + // the ability to merge between a modified and renamed file. + MergeTreeFindRenames MergeTreeFlag = C.GIT_MERGE_FIND_RENAMES + // If a conflict occurs, exit immediately instead of attempting to + // continue resolving conflicts. The merge operation will fail with + // GIT_EMERGECONFLICT and no index will be returned. + MergeTreeFailOnConflict MergeTreeFlag = C.GIT_MERGE_FAIL_ON_CONFLICT +) + +type MergeOptions struct { + Version uint + TreeFlags MergeTreeFlag + + RenameThreshold uint + TargetLimit uint + FileFavor MergeFileFavor + + //TODO: Diff similarity metric +} + +func mergeOptionsFromC(opts *C.git_merge_options) MergeOptions { + return MergeOptions{ + Version: uint(opts.version), + TreeFlags: MergeTreeFlag(opts.flags), + RenameThreshold: uint(opts.rename_threshold), + TargetLimit: uint(opts.target_limit), + FileFavor: MergeFileFavor(opts.file_favor), + } +} + +func DefaultMergeOptions() (MergeOptions, error) { + opts := C.git_merge_options{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_merge_init_options(&opts, C.GIT_MERGE_OPTIONS_VERSION) + if ecode < 0 { + return MergeOptions{}, MakeGitError(ecode) + } + return mergeOptionsFromC(&opts), nil +} + +func (mo *MergeOptions) toC() *C.git_merge_options { + if mo == nil { + return nil + } + return &C.git_merge_options{ + version: C.uint(mo.Version), + flags: C.git_merge_flag_t(mo.TreeFlags), + rename_threshold: C.uint(mo.RenameThreshold), + target_limit: C.uint(mo.TargetLimit), + file_favor: C.git_merge_file_favor_t(mo.FileFavor), + } +} + +type MergeFileFavor int + +const ( + MergeFileFavorNormal MergeFileFavor = C.GIT_MERGE_FILE_FAVOR_NORMAL + MergeFileFavorOurs MergeFileFavor = C.GIT_MERGE_FILE_FAVOR_OURS + MergeFileFavorTheirs MergeFileFavor = C.GIT_MERGE_FILE_FAVOR_THEIRS + MergeFileFavorUnion MergeFileFavor = C.GIT_MERGE_FILE_FAVOR_UNION +) + +func (r *Repository) Merge(theirHeads []*AnnotatedCommit, mergeOptions *MergeOptions, checkoutOptions *CheckoutOpts) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cMergeOpts := mergeOptions.toC() + cCheckoutOpts := checkoutOptions.toC() + defer freeCheckoutOpts(cCheckoutOpts) + + gmerge_head_array := make([]*C.git_annotated_commit, len(theirHeads)) + for i := 0; i < len(theirHeads); i++ { + gmerge_head_array[i] = theirHeads[i].ptr + } + ptr := unsafe.Pointer(&gmerge_head_array[0]) + err := C.git_merge(r.ptr, (**C.git_annotated_commit)(ptr), C.size_t(len(theirHeads)), cMergeOpts, cCheckoutOpts) + runtime.KeepAlive(theirHeads) + if err < 0 { + return MakeGitError(err) + } + return nil +} + +type MergeAnalysis int + +const ( + MergeAnalysisNone MergeAnalysis = C.GIT_MERGE_ANALYSIS_NONE + MergeAnalysisNormal MergeAnalysis = C.GIT_MERGE_ANALYSIS_NORMAL + MergeAnalysisUpToDate MergeAnalysis = C.GIT_MERGE_ANALYSIS_UP_TO_DATE + MergeAnalysisFastForward MergeAnalysis = C.GIT_MERGE_ANALYSIS_FASTFORWARD + MergeAnalysisUnborn MergeAnalysis = C.GIT_MERGE_ANALYSIS_UNBORN +) + +type MergePreference int + +const ( + MergePreferenceNone MergePreference = C.GIT_MERGE_PREFERENCE_NONE + MergePreferenceNoFastForward MergePreference = C.GIT_MERGE_PREFERENCE_NO_FASTFORWARD + MergePreferenceFastForwardOnly MergePreference = C.GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY +) + +// MergeAnalysis returns the possible actions which could be taken by +// a 'git-merge' command. There may be multiple answers, so the first +// return value is a bitmask of MergeAnalysis values. +func (r *Repository) MergeAnalysis(theirHeads []*AnnotatedCommit) (MergeAnalysis, MergePreference, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + gmerge_head_array := make([]*C.git_annotated_commit, len(theirHeads)) + for i := 0; i < len(theirHeads); i++ { + gmerge_head_array[i] = theirHeads[i].ptr + } + ptr := unsafe.Pointer(&gmerge_head_array[0]) + var analysis C.git_merge_analysis_t + var preference C.git_merge_preference_t + err := C.git_merge_analysis(&analysis, &preference, r.ptr, (**C.git_annotated_commit)(ptr), C.size_t(len(theirHeads))) + runtime.KeepAlive(theirHeads) + if err < 0 { + return MergeAnalysisNone, MergePreferenceNone, MakeGitError(err) + } + return MergeAnalysis(analysis), MergePreference(preference), nil + +} + +func (r *Repository) MergeCommits(ours *Commit, theirs *Commit, options *MergeOptions) (*Index, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + copts := options.toC() + + var ptr *C.git_index + ret := C.git_merge_commits(&ptr, r.ptr, ours.cast_ptr, theirs.cast_ptr, copts) + runtime.KeepAlive(ours) + runtime.KeepAlive(theirs) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newIndexFromC(ptr, r), nil +} + +func (r *Repository) MergeTrees(ancestor *Tree, ours *Tree, theirs *Tree, options *MergeOptions) (*Index, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + copts := options.toC() + + var ancestor_ptr *C.git_tree + if ancestor != nil { + ancestor_ptr = ancestor.cast_ptr + } + var ptr *C.git_index + ret := C.git_merge_trees(&ptr, r.ptr, ancestor_ptr, ours.cast_ptr, theirs.cast_ptr, copts) + runtime.KeepAlive(ancestor) + runtime.KeepAlive(ours) + runtime.KeepAlive(theirs) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newIndexFromC(ptr, r), nil +} + +func (r *Repository) MergeBase(one *Oid, two *Oid) (*Oid, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var oid C.git_oid + ret := C.git_merge_base(&oid, r.ptr, one.toC(), two.toC()) + runtime.KeepAlive(one) + runtime.KeepAlive(two) + runtime.KeepAlive(r) + if ret < 0 { + return nil, MakeGitError(ret) + } + return newOidFromC(&oid), nil +} + +// MergeBases retrieves the list of merge bases between two commits. +// +// If none are found, an empty slice is returned and the error is set +// approprately +func (r *Repository) MergeBases(one, two *Oid) ([]*Oid, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var coids C.git_oidarray + ret := C.git_merge_bases(&coids, r.ptr, one.toC(), two.toC()) + runtime.KeepAlive(one) + runtime.KeepAlive(two) + if ret < 0 { + return make([]*Oid, 0), MakeGitError(ret) + } + + oids := make([]*Oid, coids.count) + hdr := reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer(coids.ids)), + Len: int(coids.count), + Cap: int(coids.count), + } + + goSlice := *(*[]C.git_oid)(unsafe.Pointer(&hdr)) + + for i, cid := range goSlice { + oids[i] = newOidFromC(&cid) + } + + return oids, nil +} + +//TODO: int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]); +//TODO: GIT_EXTERN(int) git_merge_base_octopus(git_oid *out,git_repository *repo,size_t length,const git_oid input_array[]); + +type MergeFileResult struct { + Automergeable bool + Path string + Mode uint + Contents []byte + ptr *C.git_merge_file_result +} + +func newMergeFileResultFromC(c *C.git_merge_file_result) *MergeFileResult { + var path string + if c.path != nil { + path = C.GoString(c.path) + } + + originalBytes := C.GoBytes(unsafe.Pointer(c.ptr), C.int(c.len)) + gobytes := make([]byte, len(originalBytes)) + copy(gobytes, originalBytes) + r := &MergeFileResult{ + Automergeable: c.automergeable != 0, + Path: path, + Mode: uint(c.mode), + Contents: gobytes, + ptr: c, + } + + runtime.SetFinalizer(r, (*MergeFileResult).Free) + return r +} + +func (r *MergeFileResult) Free() { + runtime.SetFinalizer(r, nil) + C.git_merge_file_result_free(r.ptr) +} + +type MergeFileInput struct { + Path string + Mode uint + Contents []byte +} + +type MergeFileFlags int + +const ( + MergeFileDefault MergeFileFlags = C.GIT_MERGE_FILE_DEFAULT + + MergeFileStyleMerge MergeFileFlags = C.GIT_MERGE_FILE_STYLE_MERGE + MergeFileStyleDiff MergeFileFlags = C.GIT_MERGE_FILE_STYLE_DIFF3 + MergeFileStyleSimplifyAlnum MergeFileFlags = C.GIT_MERGE_FILE_SIMPLIFY_ALNUM +) + +type MergeFileOptions struct { + AncestorLabel string + OurLabel string + TheirLabel string + Favor MergeFileFavor + Flags MergeFileFlags + MarkerSize uint16 +} + +func mergeFileOptionsFromC(c C.git_merge_file_options) MergeFileOptions { + return MergeFileOptions{ + AncestorLabel: C.GoString(c.ancestor_label), + OurLabel: C.GoString(c.our_label), + TheirLabel: C.GoString(c.their_label), + Favor: MergeFileFavor(c.favor), + Flags: MergeFileFlags(c.flags), + MarkerSize: uint16(c.marker_size), + } +} + +func populateCMergeFileOptions(c *C.git_merge_file_options, options MergeFileOptions) { + c.ancestor_label = C.CString(options.AncestorLabel) + c.our_label = C.CString(options.OurLabel) + c.their_label = C.CString(options.TheirLabel) + c.favor = C.git_merge_file_favor_t(options.Favor) + c.flags = C.git_merge_file_flag_t(options.Flags) + c.marker_size = C.ushort(options.MarkerSize) +} + +func freeCMergeFileOptions(c *C.git_merge_file_options) { + C.free(unsafe.Pointer(c.ancestor_label)) + C.free(unsafe.Pointer(c.our_label)) + C.free(unsafe.Pointer(c.their_label)) +} + +func MergeFile(ancestor MergeFileInput, ours MergeFileInput, theirs MergeFileInput, options *MergeFileOptions) (*MergeFileResult, error) { + + ancestorPath := C.CString(ancestor.Path) + defer C.free(unsafe.Pointer(ancestorPath)) + var ancestorContents *byte + if len(ancestor.Contents) > 0 { + ancestorContents = &ancestor.Contents[0] + } + + oursPath := C.CString(ours.Path) + defer C.free(unsafe.Pointer(oursPath)) + var oursContents *byte + if len(ours.Contents) > 0 { + oursContents = &ours.Contents[0] + } + + theirsPath := C.CString(theirs.Path) + defer C.free(unsafe.Pointer(theirsPath)) + var theirsContents *byte + if len(theirs.Contents) > 0 { + theirsContents = &theirs.Contents[0] + } + + var copts *C.git_merge_file_options + if options != nil { + copts = &C.git_merge_file_options{} + ecode := C.git_merge_file_init_options(copts, C.GIT_MERGE_FILE_OPTIONS_VERSION) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + populateCMergeFileOptions(copts, *options) + defer freeCMergeFileOptions(copts) + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var result C.git_merge_file_result + ecode := C._go_git_merge_file(&result, + (*C.char)(unsafe.Pointer(ancestorContents)), C.size_t(len(ancestor.Contents)), ancestorPath, C.uint(ancestor.Mode), + (*C.char)(unsafe.Pointer(oursContents)), C.size_t(len(ours.Contents)), oursPath, C.uint(ours.Mode), + (*C.char)(unsafe.Pointer(theirsContents)), C.size_t(len(theirs.Contents)), theirsPath, C.uint(theirs.Mode), + copts) + runtime.KeepAlive(ancestor) + runtime.KeepAlive(ours) + runtime.KeepAlive(theirs) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newMergeFileResultFromC(&result), nil + +} + +// TODO: GIT_EXTERN(int) git_merge_file_from_index(git_merge_file_result *out,git_repository *repo,const git_index_entry *ancestor, const git_index_entry *ours, const git_index_entry *theirs, const git_merge_file_options *opts); |