diff options
Diffstat (limited to 'merge.go')
| -rw-r--r-- | merge.go | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/merge.go b/merge.go new file mode 100644 index 0000000..48a28b0 --- /dev/null +++ b/merge.go @@ -0,0 +1,354 @@ +package git + +/* +#include <git2.h> +#include <git2/errors.h> + +extern git_merge_head** _go_git_make_merge_head_array(size_t len); +extern void _go_git_merge_head_array_set(git_merge_head** array, git_merge_head* ptr, size_t n); +extern git_merge_head* _go_git_merge_head_array_get(git_merge_head** array, size_t n); + +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +type MergeHead struct { + ptr *C.git_merge_head +} + +func newMergeHeadFromC(c *C.git_merge_head) *MergeHead { + mh := &MergeHead{ptr: c} + runtime.SetFinalizer(mh, (*MergeHead).Free) + return mh +} + +func (mh *MergeHead) Free() { + runtime.SetFinalizer(mh, nil) + C.git_merge_head_free(mh.ptr) +} + +func (r *Repository) MergeHeadFromFetchHead(branchName string, remoteURL string, oid *Oid) (*MergeHead, error) { + mh := &MergeHead{} + + cbranchName := C.CString(branchName) + defer C.free(unsafe.Pointer(cbranchName)) + + cremoteURL := C.CString(remoteURL) + defer C.free(unsafe.Pointer(cremoteURL)) + + ret := C.git_merge_head_from_fetchhead(&mh.ptr, r.ptr, cbranchName, cremoteURL, oid.toC()) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(mh, (*MergeHead).Free) + return mh, nil +} + +func (r *Repository) MergeHeadFromId(oid *Oid) (*MergeHead, error) { + mh := &MergeHead{} + + ret := C.git_merge_head_from_id(&mh.ptr, r.ptr, oid.toC()) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(mh, (*MergeHead).Free) + return mh, nil +} + +func (r *Repository) MergeHeadFromRef(ref *Reference) (*MergeHead, error) { + mh := &MergeHead{} + + ret := C.git_merge_head_from_ref(&mh.ptr, r.ptr, ref.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(mh, (*MergeHead).Free) + return mh, nil +} + +type MergeTreeFlag int + +const ( + MergeTreeFindRenames MergeTreeFlag = C.GIT_MERGE_TREE_FIND_RENAMES +) + +type MergeOptions struct { + Version uint + Flags 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), + Flags: 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{} + 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_tree_flag_t(mo.Flags), + 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 = C.GIT_MERGE_FILE_FAVOR_OURS + MergeFileFavorTheirs = C.GIT_MERGE_FILE_FAVOR_THEIRS + MergeFileFavorUnion = C.GIT_MERGE_FILE_FAVOR_UNION +) + +func (r *Repository) Merge(theirHeads []*MergeHead, mergeOptions *MergeOptions, checkoutOptions *CheckoutOpts) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cMergeOpts := mergeOptions.toC() + cCheckoutOpts := checkoutOptions.toC() + + gmerge_head_array := make([]*C.git_merge_head, 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_merge_head)(ptr), C.size_t(len(theirHeads)), cMergeOpts, cCheckoutOpts) + if err < 0 { + return MakeGitError(err) + } + return nil +} + +type MergeAnalysis int + +const ( + MergeAnalysisNone MergeAnalysis = C.GIT_MERGE_ANALYSIS_NONE + MergeAnalysisNormal = C.GIT_MERGE_ANALYSIS_NORMAL + MergeAnalysisUpToDate = C.GIT_MERGE_ANALYSIS_UP_TO_DATE + MergeAnalysisFastForward = C.GIT_MERGE_ANALYSIS_FASTFORWARD + MergeAnalysisUnborn = C.GIT_MERGE_ANALYSIS_UNBORN +) + +func (r *Repository) MergeAnalysis(theirHeads []*MergeHead) (MergeAnalysis, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + gmerge_head_array := make([]*C.git_merge_head, 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 + err := C.git_merge_analysis(&analysis, r.ptr, (**C.git_merge_head)(ptr), C.size_t(len(theirHeads))) + if err < 0 { + return MergeAnalysisNone, MakeGitError(err) + } + return MergeAnalysis(analysis), nil + +} + +func (r *Repository) MergeCommits(ours *Commit, theirs *Commit, options *MergeOptions) (*Index, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + copts := options.toC() + + idx := &Index{} + + ret := C.git_merge_commits(&idx.ptr, r.ptr, ours.cast_ptr, theirs.cast_ptr, copts) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(idx, (*Index).Free) + return idx, nil +} + +func (r *Repository) MergeTrees(ancestor *Tree, ours *Tree, theirs *Tree, options *MergeOptions) (*Index, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + copts := options.toC() + + idx := &Index{} + + ret := C.git_merge_trees(&idx.ptr, r.ptr, ancestor.cast_ptr, ours.cast_ptr, theirs.cast_ptr, copts) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(idx, (*Index).Free) + return idx, 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()) + if ret < 0 { + return nil, MakeGitError(ret) + } + return newOidFromC(&oid), 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 +} + +// populate a C struct with merge file input, make sure to use freeMergeFileInput to clean up allocs +func populateCMergeFileInput(c *C.git_merge_file_input, input MergeFileInput) { + c.path = C.CString(input.Path) + c.ptr = (*C.char)(unsafe.Pointer(&input.Contents[0])) + c.size = C.size_t(len(input.Contents)) + c.mode = C.uint(input.Mode) +} + +func freeCMergeFileInput(c *C.git_merge_file_input) { + C.free(unsafe.Pointer(c.path)) +} + +type MergeFileFlags int + +const ( + MergeFileDefault MergeFileFlags = C.GIT_MERGE_FILE_DEFAULT + + MergeFileStyleMerge = C.GIT_MERGE_FILE_STYLE_MERGE + MergeFileStyleDiff = C.GIT_MERGE_FILE_STYLE_DIFF3 + MergeFileStyleSimplifyAlnum = C.GIT_MERGE_FILE_SIMPLIFY_ALNUM +) + +type MergeFileOptions struct { + AncestorLabel string + OurLabel string + TheirLabel string + Favor MergeFileFavor + Flags MergeFileFlags +} + +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), + } +} + +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_flags_t(options.Flags) +} + +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) { + + var cancestor C.git_merge_file_input + var cours C.git_merge_file_input + var ctheirs C.git_merge_file_input + + populateCMergeFileInput(&cancestor, ancestor) + defer freeCMergeFileInput(&cancestor) + populateCMergeFileInput(&cours, ours) + defer freeCMergeFileInput(&cours) + populateCMergeFileInput(&ctheirs, theirs) + defer freeCMergeFileInput(&ctheirs) + + 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) + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var result C.git_merge_file_result + ecode := C.git_merge_file(&result, &cancestor, &cours, &ctheirs, copts) + 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); |
