github.com/erikjuhani/git-gong@v0.0.0-20220213141213-6b9fa82d4e7c/gong/repository.go (about)

     1  package gong
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"net/url"
     9  	"os"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/erikjuhani/git-gong/config"
    14  	git "github.com/libgit2/git2go/v31"
    15  )
    16  
    17  // Repository represents is an abstraction of the underlying *git.Repository.
    18  // To access the essence (*git.Repository) call Essence() function.
    19  type Repository struct {
    20  	Head    *Head
    21  	Path    string
    22  	GitPath string
    23  	Index   *git.Index
    24  	Tree    *git.Tree
    25  	essence *git.Repository
    26  	Stashes *StashCollection
    27  }
    28  
    29  // Free frees git repository pointer.
    30  func (repo *Repository) Free() {
    31  	repo.Essence().Free()
    32  }
    33  
    34  // Essence returns *git.Repository.
    35  func (repo *Repository) Essence() *git.Repository {
    36  	return repo.essence
    37  }
    38  
    39  func (repo *Repository) FindTree(treeID *git.Oid) (*git.Tree, error) {
    40  	return repo.Essence().LookupTree(treeID)
    41  }
    42  
    43  func NewRepository(gitRepo *git.Repository, index *git.Index) *Repository {
    44  	return &Repository{
    45  		Head:    NewHead(gitRepo),
    46  		Path:    gitRepo.Workdir(),
    47  		GitPath: gitRepo.Path(),
    48  		Index:   index,
    49  		Stashes: NewStashCollection(&gitRepo.Stashes),
    50  		essence: gitRepo,
    51  	}
    52  }
    53  
    54  // Init initializes the repository.
    55  // TODO: use the git config to set the initial default branch.
    56  // more info: https://github.blog/2020-07-27-highlights-from-git-2-28/#introducing-init-defaultbranch
    57  func Init(path string, bare bool, initialReference string) (*Repository, error) {
    58  	gitRepo, err := git.InitRepository(path, bare)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	if checkEmptyString(initialReference) {
    64  		initialReference = DefaultReference
    65  	}
    66  
    67  	initRef := fmt.Sprintf("%s%s", headRef, initialReference)
    68  	if err := ioutil.WriteFile(fmt.Sprintf("%s/HEAD", gitRepo.Path()), []byte("ref: "+initRef), 0644); err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	index, err := gitRepo.Index()
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	defer Free(index)
    77  
    78  	return NewRepository(gitRepo, index), nil
    79  }
    80  
    81  func (repo *Repository) DiffTreeToTree(oldTree *git.Tree, newTree *git.Tree) (*git.Diff, error) {
    82  	return repo.Essence().DiffTreeToTree(oldTree, newTree, nil)
    83  }
    84  
    85  func (repo *Repository) FindBranch(branchName string, branchType git.BranchType) (*Branch, error) {
    86  	gitBranch, err := repo.Essence().LookupBranch(branchName, branchType)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	return NewBranch(branchName, gitBranch), nil
    92  }
    93  
    94  func (repo *Repository) Merge(branchName string) error {
    95  	destinationbranch, err := repo.Head.Branch()
    96  	if err != nil {
    97  		return err
    98  	}
    99  
   100  	sourceBranch, err := repo.FindBranch(branchName, git.BranchLocal)
   101  	if err != nil {
   102  		return err
   103  	}
   104  
   105  	theirAnnCommit, err := sourceBranch.AnnotatedCommit()
   106  	if err != nil {
   107  		return err
   108  	}
   109  	defer Free(theirAnnCommit)
   110  
   111  	mergeHeads := make([]*git.AnnotatedCommit, 1)
   112  	mergeHeads[0] = theirAnnCommit
   113  
   114  	analysis, _, err := repo.essence.MergeAnalysis(mergeHeads)
   115  	log.Println(err)
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	checkoutOpts := git.CheckoutOpts{
   121  		Strategy: git.CheckoutSafe | git.CheckoutRecreateMissing | git.CheckoutUseTheirs,
   122  	}
   123  
   124  	switch {
   125  	case analysis&git.MergeAnalysisUnborn != 0:
   126  		// Head is unborn, merge is impossible
   127  		return errors.New("cannot merge head doest not exist")
   128  	case analysis&git.MergeAnalysisUpToDate != 0:
   129  		// Nothing to merge
   130  		return errors.New("merge failed, nothing to merge")
   131  	case analysis&git.MergeAnalysisFastForward != 0:
   132  		// History has not diverted so we fast forward and just add the commits on top
   133  
   134  		sourceCommit, err := repo.FindCommit(sourceBranch.ReferenceID)
   135  		if err != nil {
   136  			return err
   137  		}
   138  
   139  		tree, err := repo.FindTree(sourceCommit.Essence().TreeId())
   140  		log.Println(err)
   141  		if err != nil {
   142  			return err
   143  		}
   144  
   145  		if err := repo.CheckoutTree(tree, &checkoutOpts); err != nil {
   146  			return err
   147  		}
   148  
   149  		if err := repo.Head.SetReference(sourceBranch.RefName); err != nil {
   150  			return err
   151  		}
   152  
   153  		return nil
   154  	case analysis&git.MergeAnalysisNormal != 0:
   155  		mergeOpts, err := git.DefaultMergeOptions()
   156  		if err != nil {
   157  			return err
   158  		}
   159  
   160  		mergeOpts.FileFavor = git.MergeFileFavorNormal
   161  		mergeOpts.TreeFlags = git.MergeTreeFailOnConflict
   162  
   163  		if err := repo.Essence().Merge(mergeHeads, &mergeOpts, &checkoutOpts); err != nil {
   164  			return err
   165  		}
   166  
   167  		index, err := repo.Essence().Index()
   168  		if err != nil {
   169  			return err
   170  		}
   171  		defer Free(index)
   172  
   173  		if index.HasConflicts() {
   174  			return errors.New("merge conflict, cannot merge. Fix conflicts then commit before merge")
   175  		}
   176  
   177  		theirCommit, err := repo.FindCommit(sourceBranch.ReferenceID)
   178  		if err != nil {
   179  			return err
   180  		}
   181  		defer Free(theirCommit)
   182  
   183  		treeID, err := index.WriteTree()
   184  		if err != nil {
   185  			return err
   186  		}
   187  
   188  		tree, err := repo.FindTree(treeID)
   189  		if err != nil {
   190  			return err
   191  		}
   192  		defer Free(tree)
   193  
   194  		ourCommit, err := repo.Head.Commit()
   195  		if err != nil {
   196  			return err
   197  		}
   198  		defer Free(ourCommit)
   199  
   200  		mergeMessage := fmt.Sprintf("Merge %s into %s", sourceBranch.Name, destinationbranch.Name)
   201  
   202  		mergeCommit, err := repo.CreateCommit(tree, mergeMessage, ourCommit, theirCommit)
   203  		if err != nil {
   204  			return err
   205  		}
   206  		defer Free(mergeCommit)
   207  
   208  		return repo.Essence().StateCleanup()
   209  	}
   210  
   211  	return nil
   212  }
   213  
   214  func (repo *Repository) Info() (string, error) {
   215  	currentBranch, err := repo.CurrentBranch()
   216  	if err != nil {
   217  		return "", err
   218  	}
   219  
   220  	currentTip, err := repo.Head.Commit()
   221  	if err != nil {
   222  		return "", err
   223  	}
   224  
   225  	sb := strings.Builder{}
   226  
   227  	sb.WriteString(fmt.Sprintf("Branch %s\n", currentBranch.Name))
   228  	sb.WriteString(fmt.Sprintf("Commit %s\n\n", currentTip.ID.String()))
   229  
   230  	entries, err := repo.StatusEntries()
   231  	if err != nil {
   232  		return "", err
   233  	}
   234  
   235  	if len(entries) == 0 {
   236  		sb.WriteString("No changes.")
   237  		return strings.TrimSuffix(sb.String(), "\n"), nil
   238  	}
   239  
   240  	sb.WriteString(fmt.Sprintf("Changes (%d):\n", len(entries)))
   241  	for _, e := range entries {
   242  		switch e.IndexToWorkdir.Status {
   243  		case git.DeltaAdded:
   244  			sb.WriteString(" A ")
   245  		case git.DeltaModified:
   246  			sb.WriteString(" M ")
   247  		case git.DeltaRenamed:
   248  			sb.WriteString(" R ")
   249  		case git.DeltaDeleted:
   250  			sb.WriteString(" D ")
   251  		case git.DeltaUntracked:
   252  			sb.WriteString("?? ")
   253  		}
   254  
   255  		sb.WriteString(fmt.Sprintf("%s\n", e.IndexToWorkdir.NewFile.Path))
   256  	}
   257  
   258  	return strings.TrimSuffix(sb.String(), "\n"), nil
   259  }
   260  
   261  func (repo *Repository) StatusEntries() ([]git.StatusEntry, error) {
   262  	opts := &git.StatusOptions{
   263  		Show:  git.StatusShowIndexAndWorkdir,
   264  		Flags: git.StatusOptIncludeUntracked | git.StatusOptRenamesHeadToIndex | git.StatusOptSortCaseSensitively,
   265  	}
   266  
   267  	var entries []git.StatusEntry
   268  
   269  	statusList, err := repo.Essence().StatusList(opts)
   270  	if err != nil {
   271  		return entries, err
   272  	}
   273  	defer Free(statusList)
   274  
   275  	entryCount, err := statusList.EntryCount()
   276  	if err != nil {
   277  		return entries, err
   278  	}
   279  
   280  	for i := 0; i < entryCount; i++ {
   281  		entry, err := statusList.ByIndex(i)
   282  		if err != nil {
   283  			continue
   284  		}
   285  		entries = append(entries, entry)
   286  	}
   287  
   288  	return entries, nil
   289  }
   290  
   291  func (repo *Repository) UndoLastCommit() (*Commit, error) {
   292  	commits, err := repo.Commits()
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  
   297  	var idx int
   298  
   299  	if len(commits) > 1 {
   300  		idx = 1
   301  	}
   302  
   303  	tip := commits[0]
   304  
   305  	if err := repo.Essence().ResetToCommit(commits[idx].Essence(), git.ResetSoft, &git.CheckoutOptions{}); err != nil {
   306  		return nil, err
   307  	}
   308  
   309  	return tip, nil
   310  }
   311  
   312  func (repo *Repository) CurrentBranch() (*Branch, error) {
   313  	return repo.Head.Branch()
   314  }
   315  
   316  func (repo *Repository) Tags() ([]*Tag, error) {
   317  	var tags []*Tag
   318  	err := repo.Essence().Tags.Foreach(func(name string, _ *git.Oid) error {
   319  		ref, err := repo.Essence().References.Lookup(name)
   320  		if err != nil {
   321  			return err
   322  		}
   323  		defer Free(ref)
   324  
   325  		if ref.IsTag() {
   326  			tagObj, err := ref.Peel(git.ObjectTag)
   327  			if err != nil {
   328  				return err
   329  			}
   330  
   331  			tag, err := tagObj.AsTag()
   332  			if err != nil {
   333  				return err
   334  			}
   335  			defer Free(tag)
   336  
   337  			tags = append(tags, NewTag(tag))
   338  		}
   339  
   340  		return nil
   341  	})
   342  
   343  	if err != nil {
   344  		return tags, err
   345  	}
   346  
   347  	return tags, nil
   348  }
   349  
   350  func (repo *Repository) FindTag(tagName string) (*Tag, error) {
   351  	tags, err := repo.Tags()
   352  	if err != nil {
   353  		return nil, err
   354  	}
   355  
   356  	var tag *Tag
   357  
   358  	for _, tag = range tags {
   359  		if tag.Name == tagName {
   360  			break
   361  		}
   362  	}
   363  
   364  	if tag == nil {
   365  		return nil, fmt.Errorf("no tag found by tag name %s", tagName)
   366  	}
   367  	defer Free(tag)
   368  
   369  	return tag, nil
   370  }
   371  
   372  func (repo *Repository) FindCommit(commitID *git.Oid) (*Commit, error) {
   373  	gitCommit, err := repo.Essence().LookupCommit(commitID)
   374  	if err != nil {
   375  		return nil, err
   376  	}
   377  
   378  	return NewCommit(gitCommit), nil
   379  }
   380  
   381  func (repo *Repository) CheckoutTree(tree *git.Tree, opts *git.CheckoutOptions) error {
   382  	return repo.Essence().CheckoutTree(tree, opts)
   383  }
   384  
   385  func (repo *Repository) CheckoutTag(tagName string) (*Tag, error) {
   386  	checkoutOpts := &git.CheckoutOpts{
   387  		Strategy: git.CheckoutSafe | git.CheckoutRecreateMissing | git.CheckoutAllowConflicts | git.CheckoutUseTheirs,
   388  	}
   389  
   390  	tags, err := repo.Tags()
   391  	if err != nil {
   392  		return nil, err
   393  	}
   394  
   395  	var tag *Tag
   396  
   397  	for _, tag = range tags {
   398  		if tag.Name == tagName {
   399  			break
   400  		}
   401  	}
   402  
   403  	if tag == nil {
   404  		return nil, fmt.Errorf("no tag found by tag name %s", tagName)
   405  	}
   406  	defer Free(tag)
   407  
   408  	commit, err := repo.FindCommit(tag.Essence().TargetId())
   409  	if err != nil {
   410  		return nil, err
   411  	}
   412  	defer Free(commit)
   413  
   414  	tree, err := commit.Tree()
   415  	if err != nil {
   416  		return nil, err
   417  	}
   418  	defer Free(tree)
   419  
   420  	if err := repo.CheckoutTree(tree, checkoutOpts); err != nil {
   421  		return nil, err
   422  	}
   423  
   424  	if err := repo.Head.Detach(commit.ID); err != nil {
   425  		return nil, err
   426  	}
   427  
   428  	return tag, nil
   429  }
   430  
   431  func (repo *Repository) CheckoutCommit(hash string) (*Commit, error) {
   432  	checkoutOpts := &git.CheckoutOpts{
   433  		Strategy: git.CheckoutSafe | git.CheckoutRecreateMissing | git.CheckoutAllowConflicts | git.CheckoutUseTheirs,
   434  	}
   435  
   436  	commits, err := repo.Commits()
   437  	if err != nil {
   438  		return nil, err
   439  	}
   440  
   441  	var commit *Commit
   442  	for _, commit = range commits {
   443  		if commit.ID.String() == hash {
   444  			break
   445  		}
   446  	}
   447  
   448  	if commit == nil {
   449  		return nil, fmt.Errorf("no commit found by hash %s", hash)
   450  	}
   451  	defer Free(commit)
   452  
   453  	tree, err := commit.Tree()
   454  	if err != nil {
   455  		return nil, err
   456  	}
   457  	defer Free(tree)
   458  
   459  	if err := repo.Essence().CheckoutTree(tree, checkoutOpts); err != nil {
   460  		return nil, err
   461  	}
   462  
   463  	if err := repo.Head.Detach(commit.ID); err != nil {
   464  		return nil, err
   465  	}
   466  
   467  	return commit, err
   468  }
   469  
   470  func (repo *Repository) CheckoutBranch(branchName string) (*Branch, error) {
   471  	detached, err := repo.Head.IsDetached()
   472  	if err != nil {
   473  		return nil, err
   474  	}
   475  
   476  	if detached {
   477  		ref, err := repo.Essence().References.Lookup(fmt.Sprintf("%s%s", headRef, branchName))
   478  		if err != nil {
   479  			return nil, err
   480  		}
   481  		defer Free(ref)
   482  
   483  		if err := repo.Head.SetReference(ref.Name()); err != nil {
   484  			return nil, err
   485  		}
   486  
   487  		if err := repo.Head.Checkout(); err != nil {
   488  			return nil, err
   489  		}
   490  	}
   491  
   492  	branch, err := repo.FindBranch(branchName, git.BranchLocal)
   493  
   494  	// Branch does not exist, create it first
   495  	if branch == nil || err != nil {
   496  		branch, err = repo.CreateLocalBranch(branchName)
   497  		if err != nil {
   498  			return nil, err
   499  		}
   500  	}
   501  
   502  	currentBranch, err := repo.CurrentBranch()
   503  	if err != nil {
   504  		return nil, err
   505  	}
   506  
   507  	changed, err := repo.Changed()
   508  	if err != nil {
   509  		return nil, err
   510  	}
   511  
   512  	if changed {
   513  		_, err = repo.Stashes.Create(currentBranch)
   514  		if err != nil {
   515  			return nil, err
   516  		}
   517  	}
   518  
   519  	checkoutOpts := &git.CheckoutOpts{
   520  		Strategy: git.CheckoutSafe | git.CheckoutRecreateMissing | git.CheckoutAllowConflicts | git.CheckoutUseTheirs,
   521  	}
   522  
   523  	localCommit, err := repo.FindCommit(branch.ReferenceID)
   524  	if err != nil {
   525  		return nil, err
   526  	}
   527  	defer Free(localCommit)
   528  
   529  	tree, err := localCommit.Tree()
   530  	if err != nil {
   531  		return nil, err
   532  	}
   533  	defer Free(tree)
   534  
   535  	if err := repo.Essence().CheckoutTree(tree, checkoutOpts); err != nil {
   536  		return nil, err
   537  	}
   538  
   539  	if err := repo.Head.SetReference(headRef + branchName); err != nil {
   540  		return nil, err
   541  	}
   542  
   543  	// No existing stash.
   544  	if !repo.Stashes.Has(branch) {
   545  		return branch, nil
   546  	}
   547  
   548  	if err := repo.Stashes.Pop(branch); err != nil {
   549  		return nil, err
   550  	}
   551  
   552  	return branch, nil
   553  }
   554  
   555  // Clone clones a git repository from source location to a target location.
   556  // If target location is an empty string clone to a directory named after source.
   557  func Clone(source string, target string) (*Repository, error) {
   558  	opts := git.CloneOptions{}
   559  
   560  	// Check that the source is a valid url.
   561  	u, err := url.Parse(source)
   562  	if err != nil {
   563  		return nil, err
   564  	}
   565  
   566  	src := strings.TrimSuffix(u.String(), ".git")
   567  
   568  	gitRepo, err := git.Clone(src, target, &opts)
   569  	if err != nil {
   570  		return nil, err
   571  	}
   572  	defer Free(gitRepo)
   573  
   574  	return NewRepository(gitRepo, nil), nil
   575  
   576  }
   577  
   578  func Open() (repo *Repository, err error) {
   579  	wd, err := os.Getwd()
   580  	if err != nil {
   581  		return
   582  	}
   583  
   584  	gitRepo, err := git.OpenRepository(wd)
   585  	if err != nil {
   586  		return
   587  	}
   588  
   589  	index, err := gitRepo.Index()
   590  	if err != nil {
   591  		return
   592  	}
   593  	defer Free(index)
   594  
   595  	return NewRepository(gitRepo, index), nil
   596  }
   597  
   598  func (repo *Repository) Changed() (bool, error) {
   599  	diff, err := repo.Essence().DiffIndexToWorkdir(
   600  		repo.Index,
   601  		&git.DiffOptions{Flags: git.DiffIncludeUntracked},
   602  	)
   603  	if err != nil {
   604  		return false, err
   605  	}
   606  	defer diff.Free()
   607  
   608  	stats, err := diff.Stats()
   609  	if err != nil {
   610  		return false, err
   611  	}
   612  	defer stats.Free()
   613  
   614  	changeCount := stats.FilesChanged()
   615  
   616  	status, err := repo.Essence().StatusList(&git.StatusOptions{})
   617  	if err != nil {
   618  		return false, err
   619  	}
   620  	defer Free(status)
   621  
   622  	entryCount, err := status.EntryCount()
   623  	if err != nil {
   624  		return false, err
   625  	}
   626  
   627  	if changeCount == 0 && entryCount == 0 {
   628  		return false, nil
   629  	}
   630  
   631  	return true, nil
   632  }
   633  
   634  func (repo *Repository) AddToIndex(pathspec []string) (*git.Tree, error) {
   635  	branch, err := repo.Head.Branch()
   636  	if err != nil {
   637  		return nil, err
   638  	}
   639  
   640  	if config.ProtectedBranchPatterns.Match(branch.Name) {
   641  		return nil, errors.New("trying to commit on a protected branch, operation aborted")
   642  	}
   643  
   644  	changed, err := repo.Changed()
   645  	if err != nil {
   646  		return nil, err
   647  	}
   648  
   649  	if !changed {
   650  		return nil, fmt.Errorf("no files changed, %w", ErrNothingToCommit)
   651  	}
   652  
   653  	if err := repo.Index.AddAll(pathspec, git.IndexAddDefault, nil); err != nil {
   654  		return nil, err
   655  	}
   656  
   657  	treeID, err := repo.Index.WriteTree()
   658  	if err != nil {
   659  		return nil, err
   660  	}
   661  
   662  	if err = repo.Index.Write(); err != nil {
   663  		return nil, err
   664  	}
   665  
   666  	return repo.FindTree(treeID)
   667  }
   668  
   669  func (repo *Repository) CreateCommit(tree *git.Tree, message string, parents ...*Commit) (*Commit, error) {
   670  	// Implement later.
   671  	/*
   672  		if checkEmptyString(msg) {
   673  			input, cliErr := cli.CaptureInput()
   674  			if cliErr != nil {
   675  				return nil, cliErr
   676  			}
   677  
   678  			msg = string(input)
   679  		}
   680  	*/
   681  
   682  	if checkEmptyString(message) {
   683  		return nil, ErrEmptyCommitMsg
   684  	}
   685  
   686  	exists, err := repo.Head.Exists()
   687  	if err != nil {
   688  		return nil, err
   689  	}
   690  
   691  	var commitID *git.Oid
   692  
   693  	if exists {
   694  		headCommit, err := repo.Head.Commit()
   695  		defer Free(headCommit)
   696  
   697  		if err != nil {
   698  			return nil, err
   699  		}
   700  
   701  		gitCommits := []*git.Commit{headCommit.Essence()}
   702  		for _, c := range parents {
   703  			gitCommits = append(gitCommits, c.Essence())
   704  		}
   705  
   706  		commitID, err = repo.Essence().CreateCommit(
   707  			repo.Head.RefName,
   708  			signature(),
   709  			signature(),
   710  			message,
   711  			tree,
   712  			gitCommits...,
   713  		)
   714  		if err != nil {
   715  			return nil, err
   716  		}
   717  	} else {
   718  		// Initial commit.
   719  		commitID, err = repo.Essence().CreateCommit(repo.Head.RefName, signature(), signature(), message, tree)
   720  		if err != nil {
   721  			return nil, err
   722  		}
   723  	}
   724  
   725  	if err := repo.Head.Checkout(); err != nil {
   726  		return nil, err
   727  	}
   728  
   729  	return repo.FindCommit(commitID)
   730  }
   731  
   732  func (repo *Repository) References() ([]string, error) {
   733  	iter, err := repo.Essence().NewReferenceIterator()
   734  	if err != nil {
   735  		return nil, err
   736  	}
   737  	defer Free(iter)
   738  
   739  	var list []string
   740  
   741  	nameIter := iter.Names()
   742  	name, err := nameIter.Next()
   743  	for err == nil {
   744  		list = append(list, name)
   745  		name, err = nameIter.Next()
   746  	}
   747  
   748  	return list, err
   749  }
   750  
   751  func (repo *Repository) Commits() ([]*Commit, error) {
   752  	currentTip, err := repo.Head.Commit()
   753  	if err != nil {
   754  		return nil, err
   755  	}
   756  	defer Free(currentTip)
   757  
   758  	commits := []*Commit{currentTip}
   759  
   760  	parent := currentTip
   761  	for parent.HasChildren() {
   762  		parent = parent.Parent()
   763  		commits = append(commits, parent)
   764  	}
   765  
   766  	return commits, nil
   767  }
   768  
   769  // CreateTag creates a git tag.
   770  func (repo *Repository) CreateTag(tagname string, message string) (tag *Tag, err error) {
   771  	headCommit, err := repo.Head.Commit()
   772  	if err != nil {
   773  		return
   774  	}
   775  	defer Free(headCommit)
   776  
   777  	gitTag, err := repo.Essence().Tags.Create(tagname, headCommit.Essence(), signature(), message)
   778  	if err != nil {
   779  		return
   780  	}
   781  
   782  	return &Tag{ID: gitTag, Name: tagname}, nil
   783  }
   784  
   785  // CreateLocalBranch creates a local branch to repository.
   786  func (repo *Repository) CreateLocalBranch(branchName string) (branch *Branch, err error) {
   787  	// Check if branch already exists
   788  	localBranch, err := repo.FindBranch(branchName, git.BranchLocal)
   789  	if localBranch != nil && err != nil {
   790  		return
   791  	}
   792  
   793  	// Branch already exists return existing branch and an error stating branch already exists.
   794  	if localBranch != nil {
   795  		return localBranch, fmt.Errorf("branch %s already exists", branchName)
   796  	}
   797  
   798  	headCommit, err := repo.Head.Commit()
   799  	if err != nil {
   800  		return
   801  	}
   802  	defer Free(headCommit)
   803  
   804  	return repo.createBranch(branchName, headCommit, false)
   805  }
   806  
   807  func (repo *Repository) createBranch(branchName string, commit *Commit, force bool) (*Branch, error) {
   808  	if !config.AllowedBranchPatterns.Match(branchName) {
   809  		return nil, errors.New("error branch name did not match allowed template patterns")
   810  	}
   811  
   812  	gitBranch, err := repo.Essence().CreateBranch(branchName, commit.Essence(), force)
   813  	if err != nil {
   814  		return nil, err
   815  	}
   816  
   817  	return NewBranch(branchName, gitBranch), nil
   818  }
   819  
   820  // TODO get signature from git configuration
   821  func signature() *git.Signature {
   822  	return &git.Signature{
   823  		Name:  "gong tester",
   824  		Email: "gong@tester.com",
   825  		When:  time.Now(),
   826  	}
   827  }