ronantakizawa/codereview-bench huggingface dataset:
Repository: jesseduffield/lazygit
File: pkg/commands/pull_request.go (Go)

Code before:

	if !branchExistsOnRemote {
		return "", errors.New(pr.GitCommand.Tr.NoBranchOnRemote)
	}

	repoURL := pr.GitCommand.GetRemoteURL()
	var gitService *Service

	for _, service := range pr.GitServices {
		if strings.Contains(repoURL, service.Name) {
			gitService = service
			break
		}
	}

	if gitService == nil {
		return "", errors.New(pr.GitCommand.Tr.UnsupportedGitService)
	}

	repoInfo := getRepoInfoFromURL(repoURL)
	var toBranchName string
	if to == nil {
		toBranchName = ""
	} else {
		toBranchName = to.Name
	}
	pullRequestURL := gitService.PullRequestURL(repoInfo.Owner, repoInfo.Repository, from.Name, toBranchName)

	return pullRequestURL, nil
}

func getRepoInfoFromURL(url string) *RepoInformation {
	isHTTP := strings.HasPrefix(url, "http")

	if isHTTP {
		splits := strings.Split(url, "/")
		owner := strings.Join(splits[3:len(splits)-1], "/")
		repo := strings.TrimSuffix(splits[len(splits)-1], ".git")

		return &RepoInformation{
			Owner:      owner,
			Repository: repo,
		}
	}

	tmpSplit := strings.Split(url, ":")
	splits := strings.Split(tmpSplit[1], "/")
	owner := strings.Join(splits[0:len(splits)-1], "/")
	repo := strings.TrimSuffix(splits[len(splits)-1], ".git")

	return &RepoInformation{

Review comment (suggestion):
I think this is a bit cleaner
```suggestion
	toBranchName := ""
	if to != nil {
	        toBranchName = to.Name
	}
```

Code after:
	if !branchExistsOnRemote {
		return "", errors.New(pr.GitCommand.Tr.NoBranchOnRemote)
	}

	repoURL := pr.GitCommand.GetRemoteURL()
	var gitService *Service

	for _, service := range pr.GitServices {
		if strings.Contains(repoURL, service.Name) {
			gitService = service
			break
		}
	}

	if gitService == nil {
		return "", errors.New(pr.GitCommand.Tr.UnsupportedGitService)
	}

	repoInfo := getRepoInfoFromURL(repoURL)

	pullRequestURL := gitService.PullRequestURL(repoInfo.Owner, repoInfo.Repository, from, to)

	return pullRequestURL, nil
}

func getRepoInfoFromURL(url string) *RepoInformation {
	isHTTP := strings.HasPrefix(url, "http")

	if isHTTP {
		splits := strings.Split(url, "/")
		owner := strings.Join(splits[3:len(splits)-1], "/")
		repo := strings.TrimSuffix(splits[len(splits)-1], ".git")

		return &RepoInformation{
			Owner:      owner,
			Repository: repo,
		}
	}

	tmpSplit := strings.Split(url, ":")
	splits := strings.Split(tmpSplit[1], "/")
	owner := strings.Join(splits[0:len(splits)-1], "/")
	repo := strings.TrimSuffix(splits[len(splits)-1], ".git")

	return &RepoInformation{
		Owner:      owner,
		Repository: repo,
	}
}
Repository: jesseduffield/lazygit
File: pkg/gui/pull_request_menu_panel.go (Go)

Code before:
package gui

import (
	"fmt"
	"github.com/jesseduffield/lazygit/pkg/commands"
	"github.com/jesseduffield/lazygit/pkg/commands/models"
	"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
)

func (gui *Gui) createPullRequestMenu(selectedBranch *models.Branch, checkedOutBranch *models.Branch) error {
	menuItems := make([]*menuItem, 0, 2)

	if selectedBranch != checkedOutBranch {
		menuItems = append(menuItems, &menuItem{
			displayStrings: []string{
				fmt.Sprintf("%s -> default branch", selectedBranch.Name),
			},
			onPress: func() error {
				return createPullRequest(selectedBranch, nil, gui)
			},
		}, &menuItem{
			displayStrings: []string{
				fmt.Sprintf("%s -> %s", checkedOutBranch.Name, selectedBranch.Name),
			},
			onPress: func() error {
				return createPullRequest(checkedOutBranch, selectedBranch, gui)
			},
		})
	}

	menuItems = append(menuItems, &menuItem{
		displayStrings: []string{
			fmt.Sprintf("%s -> default branch", checkedOutBranch.Name),
		},
		onPress: func() error {
			return createPullRequest(checkedOutBranch, nil, gui)

Review comment (suggestion):
```suggestion
	menuItems := make([]*menuItem, 0, 3)
```

Code after:
package gui

import (
	"fmt"

	"github.com/jesseduffield/lazygit/pkg/commands"
	"github.com/jesseduffield/lazygit/pkg/commands/models"
	"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
)

func (gui *Gui) createPullRequestMenu(selectedBranch *models.Branch, checkedOutBranch *models.Branch) error {
	menuItems := make([]*menuItem, 0, 4)

	fromToDisplayStrings := func(from string, to string) []string {
		return []string{fmt.Sprintf("%s → %s", from, to)}
	}

	menuItemsForBranch := func(branch *models.Branch) []*menuItem {
		return []*menuItem{
			{
				displayStrings: fromToDisplayStrings(branch.Name, gui.Tr.LcDefaultBranch),
				onPress: func() error {
					return gui.createPullRequest(branch.Name, "")
				},
			},
			{
				displayStrings: fromToDisplayStrings(branch.Name, gui.Tr.LcSelectBranch),
				onPress: func() error {
					return gui.prompt(promptOpts{
						title:               branch.Name + " →",
						findSuggestionsFunc: gui.findBranchNameSuggestions,
						handleConfirm: func(targetBranchName string) error {
							return gui.createPullRequest(branch.Name, targetBranchName)
						}},
					)
				},
Repository: jesseduffield/lazygit
File: pkg/gui/mergeconflicts/find_conflicts.go (Go)

Code before:
	var newConflict *mergeConflict
	for i, line := range utils.SplitLines(content) {
		switch determineLineType(line) {
		case START:
			newConflict = &mergeConflict{start: i}
		case MIDDLE:
			newConflict.middle = i
		case END:
			newConflict.end = i
			conflicts = append(conflicts, newConflict)
			// reset value to avoid any possible silent mutations in further iterations
			newConflict = nil
		default:
			// line isn't a merge conflict marker so we just continue
		}
	}

	return conflicts
}

func determineLineType(line string) LineType {
	trimmedLine := strings.TrimPrefix(line, "++")

	mapping := map[string]LineType{
		"<<<<<<< ": START,
		"=======":  MIDDLE,
		">>>>>>> ": END,
	}

	for markerSyntax, lineType := range mapping {
		if strings.HasPrefix(trimmedLine, markerSyntax) {
			return lineType
		}
	}

	return NOT_A_MARKER
}


Review comment (suggestion):
On small thing here is that I have some files myself where I use equals as decoration e.g.:
```
=========================
HEADING
=========================
<body text>
```
If we're no longer asserting that the middle conflict marker has exactly 7 equals characters in a row, we might get some false positives. In that event, we'll actually cause a panic because we'll be doing `newConflict.middle = i` but newConflict won't be defined. So maybe we should go:
```go
if strings.HasPrefix(trimmedLine, "<<<<<<< ") {
  return START
} else if trimmedLine == "=======" {
  return START
} else if strings.HasPrefix(trimmedLine, ">>>>>>> ") {
  return END
}
```

i.e. the way you had it in the first commit haha

Code after:
	var newConflict *mergeConflict
	for i, line := range utils.SplitLines(content) {
		switch determineLineType(line) {
		case START:
			newConflict = &mergeConflict{start: i}
		case MIDDLE:
			newConflict.middle = i
		case END:
			newConflict.end = i
			conflicts = append(conflicts, newConflict)
			// reset value to avoid any possible silent mutations in further iterations
			newConflict = nil
		default:
			// line isn't a merge conflict marker so we just continue
		}
	}

	return conflicts
}

func determineLineType(line string) LineType {
	trimmedLine := strings.TrimPrefix(line, "++")

	switch {
	case strings.HasPrefix(trimmedLine, "<<<<<<< "):
		return START
	case trimmedLine == "=======":
		return MIDDLE
	case strings.HasPrefix(trimmedLine, ">>>>>>> "):
		return END
	default:
		return NOT_A_MARKER
	}
}
Repository: jesseduffield/lazygit
File: pkg/gui/view_helpers.go (Go)

Code before:

func (gui *Gui) handlePrevTab() error {
	v := getTabbedView(gui)
	if v == nil {
		return nil
	}

	return gui.onViewTabClick(
		v.Name(),
		utils.ModuloWithWrap(v.TabIndex-1, len(v.Tabs)),
	)
}

// this is the distance we will move the cursor when paging up or down in a view
func (gui *Gui) pageDelta(view *gocui.View) int {
	_, height := view.Size()

	delta := height - 1
	if delta == 0 {
		return 1
	}

	return delta
}

func getTabbedView(gui *Gui) *gocui.View {
	v := gui.g.CurrentView()
	if v == nil {
		return nil
	}
	// if the action is invoked via the Options menu,
	// we need to execute it against the parent view
	if v.Name() == "menu" {
		v = v.ParentView
	}

	return v
}


Review comment (suggestion):
I added the ParentView field to my gocui fork a while back, before I introduced the concept of Contexts. I haven't officially deprecated it but it is in essence deprecated in favour of Contexts. We have a Context stack that typically starts at the bottom with the current side view and in this case ends at the top with our menu view. The `gui.currentStaticContext()` function starts at the top of the stack and goes down until it finds a static (i.e. non-popup) context.

I reckon we should do the following:
```go
func getTabbedView(gui *Gui) *gocui.View {
	// assumes only static contexts have tabs which is an assumption I consider safe
	context := gui.currentStaticContext()
	view, _ := gui.g.View(context.GetViewName())
	return view
}
```

Code after:

func (gui *Gui) handlePrevTab() error {
	v := getTabbedView(gui)
	if v == nil {
		return nil
	}

	return gui.onViewTabClick(
		v.Name(),
		utils.ModuloWithWrap(v.TabIndex-1, len(v.Tabs)),
	)
}

// this is the distance we will move the cursor when paging up or down in a view
func (gui *Gui) pageDelta(view *gocui.View) int {
	_, height := view.Size()

	delta := height - 1
	if delta == 0 {
		return 1
	}

	return delta
}

func getTabbedView(gui *Gui) *gocui.View {
	// It safe assumption that only static contexts have tabs
	context := gui.currentStaticContext()
	view, _ := gui.g.View(context.GetViewName())
	return view
}
Repository: jesseduffield/lazygit
File: pkg/config/app_config.go (Go)

Code before:
	GetDebug() bool
	GetVersion() string
	GetCommit() string
	GetBuildDate() string
	GetName() string
	GetBuildSource() string
	GetUserConfig() *UserConfig
	GetUserConfigFiles() []string
	GetUserConfigDir() string
	GetUserConfigPath() string
	GetAppState() *AppState
	SaveAppState() error
	SetIsNewRepo(bool)
	GetIsNewRepo() bool
	ReloadUserConfig() error
}

// NewAppConfig makes a new app config
func NewAppConfig(name, version, commit, date string, buildSource string, debuggingFlag bool) (*AppConfig, error) {
	configDir, err := findOrCreateConfigDir()
	if err != nil {
		return nil, err
	}

	defaultConfigFile := filepath.Join(configDir, "config.yml")
	userConfigFiles := []string{defaultConfigFile}

	if _, err := os.Stat(defaultConfigFile); err != nil {
		if os.IsNotExist(err) {
			file, err := os.Create(defaultConfigFile)
			if err != nil {
				if strings.Contains(err.Error(), "read-only file system") {
					userConfigFiles = userConfigFiles[:0]
				} else {
					return nil, err
				}
			} else {
				file.Close()
			}
		} else {
			return nil, err
		}
	}

	userConfigFilesOverwrite := os.Getenv("LG_CONFIG_FILE")
	if userConfigFilesOverwrite != "" {
		userConfigFiles = strings.Split(userConfigFilesOverwrite, ",")
	}

	userConfig, err := loadUserConfigWithDefaults(userConfigFiles)
	if err != nil {

Review comment (suggestion):
I think we should be applying this override before the code above, so that we can handle errors with missing files etc. In fact I don't believe that validation logic needs to be moved from where it was

Code after:
type AppConfigurer interface {
	GetDebug() bool
	GetVersion() string
	GetCommit() string
	GetBuildDate() string
	GetName() string
	GetBuildSource() string
	GetUserConfig() *UserConfig
	GetUserConfigPaths() []string
	GetUserConfigDir() string
	GetTempDir() string
	GetAppState() *AppState
	SaveAppState() error
	SetIsNewRepo(bool)
	GetIsNewRepo() bool
	ReloadUserConfig() error
}

// NewAppConfig makes a new app config
func NewAppConfig(name, version, commit, date string, buildSource string, debuggingFlag bool) (*AppConfig, error) {
	configDir, err := findOrCreateConfigDir()
	if err != nil && !os.IsPermission(err) {
		return nil, err
	}

	var userConfigPaths []string
	customConfigFiles := os.Getenv("LG_CONFIG_FILE")
	if customConfigFiles != "" {
		// Load user defined config files
		userConfigPaths = strings.Split(customConfigFiles, ",")
	} else {
		// Load default config files
		userConfigPaths = []string{filepath.Join(configDir, ConfigFilename)}
	}

	userConfig, err := loadUserConfigWithDefaults(userConfigPaths)
	if err != nil {
		return nil, err
	}

	if os.Getenv("DEBUG") == "TRUE" {
		debuggingFlag = true
	}

	tempDir := filepath.Join(os.TempDir(), "lazygit")

	appState, err := loadAppState()
	if err != nil {
		return nil, err
	}
Repository: jesseduffield/lazygit
File: pkg/config/app_config.go (Go)

Code before:
}

// GetUserConfig returns the user config
func (c *AppConfig) GetUserConfig() *UserConfig {
	return c.UserConfig
}

// GetUserConfig returns the user config
func (c *AppConfig) GetUserConfigPath() string {
	return c.UserConfigPath
}

// GetAppState returns the app state
func (c *AppConfig) GetAppState() *AppState {
	return c.AppState
}

func (c *AppConfig) GetUserConfigFiles() []string {
	return c.UserConfigFiles
}

func (c *AppConfig) GetUserConfigDir() string {
	return c.UserConfigDir
}

func (c *AppConfig) ReloadUserConfig() error {
	userConfig, err := loadUserConfigWithDefaults(c.UserConfigFiles)
	if err != nil {
		return err
	}

	c.UserConfig = userConfig
	return nil
}

func configFilePath(filename string) (string, error) {
	folder, err := findOrCreateConfigDir()
	if err != nil {
		return "", err
	}

	return filepath.Join(folder, filename), nil
}

// ConfigFilename returns the filename of the current config file
func (c *AppConfig) ConfigFilename() string {
	return filepath.Join(c.UserConfigDir, "config.yml")
}

// SaveAppState marshalls the AppState struct and writes it to the disk
func (c *AppConfig) SaveAppState() error {

Review comment (suggestion):
I think we should put the validation logic back inside `loadUserConfig` so that when reloading the user config we can handle any errors gracefully (e.g. for some reason a config file might have been deleted since we started lazygit)

Code after:
// GetName returns debug flag
func (c *AppConfig) GetName() string {
	return c.Name
}

// GetBuildSource returns the source of the build. For builds from goreleaser
// this will be binaryBuild
func (c *AppConfig) GetBuildSource() string {
	return c.BuildSource
}

// GetUserConfig returns the user config
func (c *AppConfig) GetUserConfig() *UserConfig {
	return c.UserConfig
}

// GetAppState returns the app state
func (c *AppConfig) GetAppState() *AppState {
	return c.AppState
}

func (c *AppConfig) GetUserConfigPaths() []string {
	return c.UserConfigPaths
}

func (c *AppConfig) GetUserConfigDir() string {
	return c.UserConfigDir
}

func (c *AppConfig) GetTempDir() string {
	return c.TempDir
}

func (c *AppConfig) ReloadUserConfig() error {
	userConfig, err := loadUserConfigWithDefaults(c.UserConfigPaths)
	if err != nil {
		return err
	}

	c.UserConfig = userConfig
	return nil
}

func configFilePath(filename string) (string, error) {
	folder, err := findOrCreateConfigDir()
	if err != nil {
		return "", err
	}

	return filepath.Join(folder, filename), nil
}
Repository: jesseduffield/lazygit
File: pkg/gui/custom_commands.go (Go)

Code before:
				// Record matched group with group ids
				matchName := "group_" + strconv.Itoa(groupIdx)
				tmplData[matchName] = out[0][groupIdx]
				// Record last named group non-empty matches as group matches
				if group != "" {
					tmplData[group] = out[0][groupIdx]
				}
			}
		}
		err = tempItem.Execute(buffItem, tmplData)
		if err != nil {
			return candidates, gui.surfaceError(err)
		}
		err = tempDescr.Execute(buffDescr, tmplData)
		if err != nil {
			return candidates, gui.surfaceError(err)
		}

		// Populate menu entry
		// label formatted as labelFormat
		// value as valueFormat
		entry := CommandMenuEntry{
			strings.TrimSpace(buffDescr.String()),
			//"Description",
			strings.TrimSpace(buffItem.String()),
		}
		candidates = append(candidates, entry)
		buffItem.Reset()
		buffDescr.Reset()
	}
	return candidates, err
}

func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptResponses []string, responseIdx int, wrappedF func() error) error {
	// Collect cmd to run from config
	cmdStr, err := gui.resolveTemplate(prompt.Command, promptResponses)
	if err != nil {
		return gui.surfaceError(err)
	}

	// Collect Filter regexp
	filter, err := gui.resolveTemplate(prompt.Filter, promptResponses)
	if err != nil {
		return gui.surfaceError(err)
	}

	// Run and save output
	message, err := gui.GitCommand.RunCommandWithOutput(cmdStr)
	if err != nil {
		return gui.surfaceError(err)
	}

Review comment (suggestion):
I don't think we need all the comments, this explains it all :)
```suggestion
		entry := CommandMenuEntry{
			label: strings.TrimSpace(buffDescr.String()),
			value: strings.TrimSpace(buffItem.String()),
		}
```

Code after:
		tmplData := map[string]string{}
		out := reg.FindAllStringSubmatch(str, -1)
		if len(out) > 0 {
			for groupIdx, group := range reg.SubexpNames() {
				// Record matched group with group ids
				matchName := "group_" + strconv.Itoa(groupIdx)
				tmplData[matchName] = out[0][groupIdx]
				// Record last named group non-empty matches as group matches
				if group != "" {
					tmplData[group] = out[0][groupIdx]
				}
			}
		}

		err = valueTemp.Execute(buff, tmplData)
		if err != nil {
			return candidates, gui.surfaceError(err)
		}
		entry := commandMenuEntry{
			value: strings.TrimSpace(buff.String()),
		}

		if labelFormat != "" {
			buff.Reset()
			err = descTemp.Execute(buff, tmplData)
			if err != nil {
				return candidates, gui.surfaceError(err)
			}
			entry.label = strings.TrimSpace(buff.String())
		} else {
			entry.label = entry.value
		}

		candidates = append(candidates, entry)

		buff.Reset()
	}
	return candidates, err
}

func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptResponses []string, responseIdx int, wrappedF func() error) error {
	// Collect cmd to run from config
	cmdStr, err := gui.resolveTemplate(prompt.Command, promptResponses)
	if err != nil {
		return gui.surfaceError(err)
	}

	// Collect Filter regexp
	filter, err := gui.resolveTemplate(prompt.Filter, promptResponses)
	if err != nil {
		return gui.surfaceError(err)
Repository: jesseduffield/lazygit
File: pkg/gui/mergeconflicts/rendering.go (Go)

Code before:
	var outputBuffer bytes.Buffer
	for i, line := range utils.SplitLines(content) {
		textStyle := theme.DefaultTextColor
		if i == conflict.start || i == conflict.ancestor || i == conflict.target || i == conflict.end {
			textStyle = style.FgRed
		}

		if hasFocus && state.conflictIndex < len(state.conflicts) && *state.conflicts[state.conflictIndex] == *conflict && shouldHighlightLine(i, conflict, state.conflictSelection) {
			textStyle = textStyle.MergeStyle(theme.SelectedRangeBgColor).SetBold()
		}
		if i == conflict.end && len(remainingConflicts) > 0 {
			conflict, remainingConflicts = shiftConflict(remainingConflicts)
		}
		outputBuffer.WriteString(textStyle.Sprint(line) + "\n")
	}
	return outputBuffer.String()
}

func shiftConflict(conflicts []*mergeConflict) (*mergeConflict, []*mergeConflict) {
	return conflicts[0], conflicts[1:]
}

func shouldHighlightLine(index int, conflict *mergeConflict, selection Selection) bool {
	switch selection {
	case TOP:
		if conflict.ancestor >= 0 {
			return index >= conflict.start && index <= conflict.ancestor
		} else {
			return index >= conflict.start && index <= conflict.target
		}
	case MIDDLE:
		return index >= conflict.ancestor && index <= conflict.target
	case BOTTOM:
		return index >= conflict.target && index <= conflict.end
	default:
		return false
	}
}


Review comment (refactor):
could we extract `conflict.ancestor >= 0` into a method called `hasAncestor` on the mergeConflict struct, to make the meaning a little clearer?

Code after:
	var outputBuffer bytes.Buffer
	for i, line := range utils.SplitLines(content) {
		textStyle := theme.DefaultTextColor
		if conflict.isMarkerLine(i) {
			textStyle = style.FgRed
		}

		if hasFocus && state.conflictIndex < len(state.conflicts) && *state.conflicts[state.conflictIndex] == *conflict && shouldHighlightLine(i, conflict, state.Selection()) {
			textStyle = textStyle.MergeStyle(theme.SelectedRangeBgColor).SetBold()
		}
		if i == conflict.end && len(remainingConflicts) > 0 {
			conflict, remainingConflicts = shiftConflict(remainingConflicts)
		}
		outputBuffer.WriteString(textStyle.Sprint(line) + "\n")
	}
	return outputBuffer.String()
}

func shiftConflict(conflicts []*mergeConflict) (*mergeConflict, []*mergeConflict) {
	return conflicts[0], conflicts[1:]
}

func shouldHighlightLine(index int, conflict *mergeConflict, selection Selection) bool {
	selectionStart, selectionEnd := selection.bounds(conflict)
	return index >= selectionStart && index <= selectionEnd
}
Repository: jesseduffield/lazygit
File: pkg/commands/models/commit.go (Go)

Code before:

// Commit : A git commit
type Commit struct {
	Sha           string
	Name          string
	Status        string // one of "unpushed", "pushed", "merged", "rebasing" or "selected"
	Action        string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
	Tags          []string
	ExtraInfo     string // something like 'HEAD -> master, tag: v0.15.2'
	Author        string
	UnixTimestamp int64

	// SHAs of parent commits (will be multiple if it's a merge commit)
	Parents []string
}

func (c *Commit) ShortSha() string {
	return utils.ShortSha(c.Sha)
}

func (c *Commit) RefName() string {
	return c.Sha
}

func (c *Commit) ParentRefName() string {
	if len(c.Parents) == 1 && c.Parents[0] == "" {
		return EmptyTreeCommitHash
	}
	return c.Sha + "^"
}

func (c *Commit) ID() string {
	return c.RefName()
}

func (c *Commit) Description() string {
	return fmt.Sprintf("%s %s", c.Sha[:7], c.Name)
}

func (c *Commit) IsMerge() bool {
	return len(c.Parents) > 1
}

// returns true if this commit is not actually in the git log but instead
// is from a TODO file for an interactive rebase.
func (c *Commit) IsTODO() bool {
	return c.Action != ""
}


Review comment (refactor):
could we update `pkg/commands/loaders/commits.go` so that if we're dealing with the initial commit we have the parents field set to an empty slice? The fact it's currently a slice of a single empty string is just due to how we've parsed the commit log output there.

It would also be good to extract this line into its own method i.e.:
```go
func (c *Commit) IsFirstCommit() bool {
  return len(c.Parents) == 0
}
```
Just to make things more explicit

Code after:

// Commit : A git commit
type Commit struct {
	Sha           string
	Name          string
	Status        string // one of "unpushed", "pushed", "merged", "rebasing" or "selected"
	Action        string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
	Tags          []string
	ExtraInfo     string // something like 'HEAD -> master, tag: v0.15.2'
	Author        string
	UnixTimestamp int64

	// SHAs of parent commits (will be multiple if it's a merge commit)
	Parents []string
}

func (c *Commit) ShortSha() string {
	return utils.ShortSha(c.Sha)
}

func (c *Commit) RefName() string {
	return c.Sha
}

func (c *Commit) ParentRefName() string {
	if c.IsFirstCommit() {
		return EmptyTreeCommitHash
	}
	return c.RefName() + "^"
}

func (c *Commit) IsFirstCommit() bool {
	return len(c.Parents) == 0
}

func (c *Commit) ID() string {
	return c.RefName()
}

func (c *Commit) Description() string {
	return fmt.Sprintf("%s %s", c.Sha[:7], c.Name)
}

func (c *Commit) IsMerge() bool {
	return len(c.Parents) > 1
}

// returns true if this commit is not actually in the git log but instead
// is from a TODO file for an interactive rebase.
func (c *Commit) IsTODO() bool {
	return c.Action != ""