mirror of
https://github.com/harness/drone.git
synced 2025-05-05 23:42:57 +00:00
[code-1016] Replace gitea
Usages (not wrapper) (#1063)
This commit is contained in:
parent
8dc82433c5
commit
cecfecdb06
@ -141,7 +141,7 @@ func (c *Controller) Report(
|
|||||||
|
|
||||||
_, err = c.git.GetCommit(ctx, &git.GetCommitParams{
|
_, err = c.git.GetCommit(ctx, &git.GetCommitParams{
|
||||||
ReadParams: git.ReadParams{RepoUID: repo.GitUID},
|
ReadParams: git.ReadParams{RepoUID: repo.GitUID},
|
||||||
SHA: commitSHA,
|
Revision: commitSHA,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to commit sha=%s: %w", commitSHA, err)
|
return nil, fmt.Errorf("failed to commit sha=%s: %w", commitSHA, err)
|
||||||
|
@ -99,19 +99,19 @@ func (c *Controller) reportBranchEvent(
|
|||||||
branchUpdate hook.ReferenceUpdate,
|
branchUpdate hook.ReferenceUpdate,
|
||||||
) {
|
) {
|
||||||
switch {
|
switch {
|
||||||
case branchUpdate.Old == types.NilSHA:
|
case branchUpdate.Old.String() == types.NilSHA:
|
||||||
c.gitReporter.BranchCreated(ctx, &events.BranchCreatedPayload{
|
c.gitReporter.BranchCreated(ctx, &events.BranchCreatedPayload{
|
||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
PrincipalID: principalID,
|
PrincipalID: principalID,
|
||||||
Ref: branchUpdate.Ref,
|
Ref: branchUpdate.Ref,
|
||||||
SHA: branchUpdate.New,
|
SHA: branchUpdate.New.String(),
|
||||||
})
|
})
|
||||||
case branchUpdate.New == types.NilSHA:
|
case branchUpdate.New.String() == types.NilSHA:
|
||||||
c.gitReporter.BranchDeleted(ctx, &events.BranchDeletedPayload{
|
c.gitReporter.BranchDeleted(ctx, &events.BranchDeletedPayload{
|
||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
PrincipalID: principalID,
|
PrincipalID: principalID,
|
||||||
Ref: branchUpdate.Ref,
|
Ref: branchUpdate.Ref,
|
||||||
SHA: branchUpdate.Old,
|
SHA: branchUpdate.Old.String(),
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
result, err := c.git.IsAncestor(ctx, git.IsAncestorParams{
|
result, err := c.git.IsAncestor(ctx, git.IsAncestorParams{
|
||||||
@ -132,8 +132,8 @@ func (c *Controller) reportBranchEvent(
|
|||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
PrincipalID: principalID,
|
PrincipalID: principalID,
|
||||||
Ref: branchUpdate.Ref,
|
Ref: branchUpdate.Ref,
|
||||||
OldSHA: branchUpdate.Old,
|
OldSHA: branchUpdate.Old.String(),
|
||||||
NewSHA: branchUpdate.New,
|
NewSHA: branchUpdate.New.String(),
|
||||||
Forced: forced,
|
Forced: forced,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -146,27 +146,27 @@ func (c *Controller) reportTagEvent(
|
|||||||
tagUpdate hook.ReferenceUpdate,
|
tagUpdate hook.ReferenceUpdate,
|
||||||
) {
|
) {
|
||||||
switch {
|
switch {
|
||||||
case tagUpdate.Old == types.NilSHA:
|
case tagUpdate.Old.String() == types.NilSHA:
|
||||||
c.gitReporter.TagCreated(ctx, &events.TagCreatedPayload{
|
c.gitReporter.TagCreated(ctx, &events.TagCreatedPayload{
|
||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
PrincipalID: principalID,
|
PrincipalID: principalID,
|
||||||
Ref: tagUpdate.Ref,
|
Ref: tagUpdate.Ref,
|
||||||
SHA: tagUpdate.New,
|
SHA: tagUpdate.New.String(),
|
||||||
})
|
})
|
||||||
case tagUpdate.New == types.NilSHA:
|
case tagUpdate.New.String() == types.NilSHA:
|
||||||
c.gitReporter.TagDeleted(ctx, &events.TagDeletedPayload{
|
c.gitReporter.TagDeleted(ctx, &events.TagDeletedPayload{
|
||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
PrincipalID: principalID,
|
PrincipalID: principalID,
|
||||||
Ref: tagUpdate.Ref,
|
Ref: tagUpdate.Ref,
|
||||||
SHA: tagUpdate.Old,
|
SHA: tagUpdate.Old.String(),
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
c.gitReporter.TagUpdated(ctx, &events.TagUpdatedPayload{
|
c.gitReporter.TagUpdated(ctx, &events.TagUpdatedPayload{
|
||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
PrincipalID: principalID,
|
PrincipalID: principalID,
|
||||||
Ref: tagUpdate.Ref,
|
Ref: tagUpdate.Ref,
|
||||||
OldSHA: tagUpdate.Old,
|
OldSHA: tagUpdate.Old.String(),
|
||||||
NewSHA: tagUpdate.New,
|
NewSHA: tagUpdate.New.String(),
|
||||||
// tags can only be force updated!
|
// tags can only be force updated!
|
||||||
Forced: true,
|
Forced: true,
|
||||||
})
|
})
|
||||||
@ -184,7 +184,7 @@ func (c *Controller) handlePRMessaging(
|
|||||||
// skip anything that was a batch push / isn't branch related / isn't updating/creating a branch.
|
// skip anything that was a batch push / isn't branch related / isn't updating/creating a branch.
|
||||||
if len(in.RefUpdates) != 1 ||
|
if len(in.RefUpdates) != 1 ||
|
||||||
!strings.HasPrefix(in.RefUpdates[0].Ref, gitReferenceNamePrefixBranch) ||
|
!strings.HasPrefix(in.RefUpdates[0].Ref, gitReferenceNamePrefixBranch) ||
|
||||||
in.RefUpdates[0].New == types.NilSHA {
|
in.RefUpdates[0].New.String() == types.NilSHA {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,9 +188,9 @@ type changes struct {
|
|||||||
|
|
||||||
func (c *changes) groupByAction(refUpdate hook.ReferenceUpdate, name string) {
|
func (c *changes) groupByAction(refUpdate hook.ReferenceUpdate, name string) {
|
||||||
switch {
|
switch {
|
||||||
case refUpdate.Old == types.NilSHA:
|
case refUpdate.Old.String() == types.NilSHA:
|
||||||
c.created = append(c.created, name)
|
c.created = append(c.created, name)
|
||||||
case refUpdate.New == types.NilSHA:
|
case refUpdate.New.String() == types.NilSHA:
|
||||||
c.deleted = append(c.deleted, name)
|
c.deleted = append(c.deleted, name)
|
||||||
default:
|
default:
|
||||||
c.updated = append(c.updated, name)
|
c.updated = append(c.updated, name)
|
||||||
|
@ -25,7 +25,6 @@ import (
|
|||||||
events "github.com/harness/gitness/app/events/pullreq"
|
events "github.com/harness/gitness/app/events/pullreq"
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
gittypes "github.com/harness/gitness/git/types"
|
|
||||||
"github.com/harness/gitness/store"
|
"github.com/harness/gitness/store"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
@ -307,7 +306,7 @@ func setAsCodeComment(a *types.PullReqActivity, cut git.DiffCutOutput, path, sou
|
|||||||
a.Kind = enum.PullReqActivityKindChangeComment
|
a.Kind = enum.PullReqActivityKindChangeComment
|
||||||
a.CodeComment = &types.CodeCommentFields{
|
a.CodeComment = &types.CodeCommentFields{
|
||||||
Outdated: falseBool,
|
Outdated: falseBool,
|
||||||
MergeBaseSHA: cut.MergeBaseSHA,
|
MergeBaseSHA: cut.MergeBaseSHA.String(),
|
||||||
SourceSHA: sourceCommitSHA,
|
SourceSHA: sourceCommitSHA,
|
||||||
Path: path,
|
Path: path,
|
||||||
LineNew: cut.Header.NewLine,
|
LineNew: cut.Header.NewLine,
|
||||||
@ -332,7 +331,7 @@ func (c *Controller) fetchDiffCut(
|
|||||||
LineEnd: in.LineEnd,
|
LineEnd: in.LineEnd,
|
||||||
LineEndNew: in.LineEndNew,
|
LineEndNew: in.LineEndNew,
|
||||||
})
|
})
|
||||||
if errors.AsStatus(err) == errors.StatusNotFound || gittypes.IsPathNotFoundError(err) {
|
if errors.AsStatus(err) == errors.StatusNotFound {
|
||||||
return git.DiffCutOutput{}, usererror.BadRequest(errors.Message(err))
|
return git.DiffCutOutput{}, usererror.BadRequest(errors.Message(err))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -351,7 +350,7 @@ func (c *Controller) migrateCodeComment(
|
|||||||
cut git.DiffCutOutput,
|
cut git.DiffCutOutput,
|
||||||
) {
|
) {
|
||||||
needsNewLineMigrate := in.SourceCommitSHA != pr.SourceSHA
|
needsNewLineMigrate := in.SourceCommitSHA != pr.SourceSHA
|
||||||
needsOldLineMigrate := cut.MergeBaseSHA != pr.MergeBaseSHA
|
needsOldLineMigrate := cut.MergeBaseSHA.String() != pr.MergeBaseSHA
|
||||||
if !needsNewLineMigrate && !needsOldLineMigrate {
|
if !needsNewLineMigrate && !needsOldLineMigrate {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ func (c *Controller) verifyBranchExistence(ctx context.Context,
|
|||||||
branch, repo.Identifier, err)
|
branch, repo.Identifier, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ref.SHA, nil
|
return ref.SHA.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) getRepoCheckAccess(ctx context.Context,
|
func (c *Controller) getRepoCheckAccess(ctx context.Context,
|
||||||
|
@ -21,8 +21,8 @@ import (
|
|||||||
|
|
||||||
"github.com/harness/gitness/app/api/usererror"
|
"github.com/harness/gitness/app/api/usererror"
|
||||||
"github.com/harness/gitness/app/auth"
|
"github.com/harness/gitness/app/auth"
|
||||||
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
gittypes "github.com/harness/gitness/git/types"
|
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
)
|
)
|
||||||
@ -80,7 +80,7 @@ func (c *Controller) FileViewAdd(
|
|||||||
Path: in.Path,
|
Path: in.Path,
|
||||||
IncludeLatestCommit: false,
|
IncludeLatestCommit: false,
|
||||||
})
|
})
|
||||||
if err != nil && !gittypes.IsPathNotFoundError(err) {
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"failed to get tree node '%s' for provided sha '%s': %w",
|
"failed to get tree node '%s' for provided sha '%s': %w",
|
||||||
in.Path,
|
in.Path,
|
||||||
@ -102,7 +102,7 @@ func (c *Controller) FileViewAdd(
|
|||||||
Path: in.Path,
|
Path: in.Path,
|
||||||
IncludeLatestCommit: false,
|
IncludeLatestCommit: false,
|
||||||
})
|
})
|
||||||
if err != nil && !gittypes.IsPathNotFoundError(err) {
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"failed to get tree node '%s' for MergeBaseSHA '%s': %w",
|
"failed to get tree node '%s' for MergeBaseSHA '%s': %w",
|
||||||
in.Path,
|
in.Path,
|
||||||
|
@ -32,9 +32,11 @@ import (
|
|||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
gitenum "github.com/harness/gitness/git/enum"
|
gitenum "github.com/harness/gitness/git/enum"
|
||||||
|
"github.com/harness/gitness/git/sha"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
|
|
||||||
|
"github.com/gotidy/ptr"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -235,27 +237,27 @@ func (c *Controller) Merge(
|
|||||||
HeadRepoUID: sourceRepo.GitUID,
|
HeadRepoUID: sourceRepo.GitUID,
|
||||||
HeadBranch: pr.SourceBranch,
|
HeadBranch: pr.SourceBranch,
|
||||||
RefType: gitenum.RefTypeUndefined, // update no refs -> no commit will be created
|
RefType: gitenum.RefTypeUndefined, // update no refs -> no commit will be created
|
||||||
HeadExpectedSHA: in.SourceSHA,
|
HeadExpectedSHA: sha.Must(in.SourceSHA),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("merge check execution failed: %w", err)
|
return nil, nil, fmt.Errorf("merge check execution failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pr, err = c.pullreqStore.UpdateOptLock(ctx, pr, func(pr *types.PullReq) error {
|
pr, err = c.pullreqStore.UpdateOptLock(ctx, pr, func(pr *types.PullReq) error {
|
||||||
if pr.SourceSHA != mergeOutput.HeadSHA {
|
if pr.SourceSHA != mergeOutput.HeadSHA.String() {
|
||||||
return errors.New("source SHA has changed")
|
return errors.New("source SHA has changed")
|
||||||
}
|
}
|
||||||
if len(mergeOutput.ConflictFiles) > 0 {
|
if len(mergeOutput.ConflictFiles) > 0 {
|
||||||
pr.MergeCheckStatus = enum.MergeCheckStatusConflict
|
pr.MergeCheckStatus = enum.MergeCheckStatusConflict
|
||||||
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA
|
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
|
||||||
pr.MergeTargetSHA = &mergeOutput.BaseSHA
|
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
|
||||||
pr.MergeSHA = nil
|
pr.MergeSHA = nil
|
||||||
pr.MergeConflicts = mergeOutput.ConflictFiles
|
pr.MergeConflicts = mergeOutput.ConflictFiles
|
||||||
} else {
|
} else {
|
||||||
pr.MergeCheckStatus = enum.MergeCheckStatusMergeable
|
pr.MergeCheckStatus = enum.MergeCheckStatusMergeable
|
||||||
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA
|
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
|
||||||
pr.MergeTargetSHA = &mergeOutput.BaseSHA
|
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
|
||||||
pr.MergeSHA = &mergeOutput.MergeSHA
|
pr.MergeSHA = ptr.String(mergeOutput.MergeSHA.String())
|
||||||
pr.MergeConflicts = nil
|
pr.MergeConflicts = nil
|
||||||
}
|
}
|
||||||
pr.Stats.DiffStats = types.NewDiffStats(mergeOutput.CommitCount, mergeOutput.ChangedFileCount)
|
pr.Stats.DiffStats = types.NewDiffStats(mergeOutput.CommitCount, mergeOutput.ChangedFileCount)
|
||||||
@ -345,23 +347,23 @@ func (c *Controller) Merge(
|
|||||||
AuthorDate: &now,
|
AuthorDate: &now,
|
||||||
RefType: gitenum.RefTypeBranch,
|
RefType: gitenum.RefTypeBranch,
|
||||||
RefName: pr.TargetBranch,
|
RefName: pr.TargetBranch,
|
||||||
HeadExpectedSHA: in.SourceSHA,
|
HeadExpectedSHA: sha.Must(in.SourceSHA),
|
||||||
Method: gitenum.MergeMethod(in.Method),
|
Method: gitenum.MergeMethod(in.Method),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("merge check execution failed: %w", err)
|
return nil, nil, fmt.Errorf("merge check execution failed: %w", err)
|
||||||
}
|
}
|
||||||
//nolint:nestif
|
//nolint:nestif
|
||||||
if mergeOutput.MergeSHA == "" || len(mergeOutput.ConflictFiles) > 0 {
|
if mergeOutput.MergeSHA.String() == "" || len(mergeOutput.ConflictFiles) > 0 {
|
||||||
_, err = c.pullreqStore.UpdateOptLock(ctx, pr, func(pr *types.PullReq) error {
|
_, err = c.pullreqStore.UpdateOptLock(ctx, pr, func(pr *types.PullReq) error {
|
||||||
if pr.SourceSHA != mergeOutput.HeadSHA {
|
if pr.SourceSHA != mergeOutput.HeadSHA.String() {
|
||||||
return errors.New("source SHA has changed")
|
return errors.New("source SHA has changed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// update all Merge specific information
|
// update all Merge specific information
|
||||||
pr.MergeCheckStatus = enum.MergeCheckStatusConflict
|
pr.MergeCheckStatus = enum.MergeCheckStatusConflict
|
||||||
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA
|
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
|
||||||
pr.MergeTargetSHA = &mergeOutput.BaseSHA
|
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
|
||||||
pr.MergeSHA = nil
|
pr.MergeSHA = nil
|
||||||
pr.MergeConflicts = mergeOutput.ConflictFiles
|
pr.MergeConflicts = mergeOutput.ConflictFiles
|
||||||
pr.Stats.DiffStats = types.NewDiffStats(mergeOutput.CommitCount, mergeOutput.ChangedFileCount)
|
pr.Stats.DiffStats = types.NewDiffStats(mergeOutput.CommitCount, mergeOutput.ChangedFileCount)
|
||||||
@ -396,10 +398,10 @@ func (c *Controller) Merge(
|
|||||||
// update all Merge specific information (might be empty if previous merge check failed)
|
// update all Merge specific information (might be empty if previous merge check failed)
|
||||||
// since this is the final operation on the PR, we update any sha that might've changed by now.
|
// since this is the final operation on the PR, we update any sha that might've changed by now.
|
||||||
pr.MergeCheckStatus = enum.MergeCheckStatusMergeable
|
pr.MergeCheckStatus = enum.MergeCheckStatusMergeable
|
||||||
pr.SourceSHA = mergeOutput.HeadSHA
|
pr.SourceSHA = mergeOutput.HeadSHA.String()
|
||||||
pr.MergeTargetSHA = &mergeOutput.BaseSHA
|
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
|
||||||
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA
|
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
|
||||||
pr.MergeSHA = &mergeOutput.MergeSHA
|
pr.MergeSHA = ptr.String(mergeOutput.MergeSHA.String())
|
||||||
pr.MergeConflicts = nil
|
pr.MergeConflicts = nil
|
||||||
pr.Stats.DiffStats = types.NewDiffStats(mergeOutput.CommitCount, mergeOutput.ChangedFileCount)
|
pr.Stats.DiffStats = types.NewDiffStats(mergeOutput.CommitCount, mergeOutput.ChangedFileCount)
|
||||||
|
|
||||||
@ -421,9 +423,9 @@ func (c *Controller) Merge(
|
|||||||
pr.ActivitySeq = activitySeqMerge
|
pr.ActivitySeq = activitySeqMerge
|
||||||
activityPayload := &types.PullRequestActivityPayloadMerge{
|
activityPayload := &types.PullRequestActivityPayloadMerge{
|
||||||
MergeMethod: in.Method,
|
MergeMethod: in.Method,
|
||||||
MergeSHA: mergeOutput.MergeSHA,
|
MergeSHA: mergeOutput.MergeSHA.String(),
|
||||||
TargetSHA: mergeOutput.BaseSHA,
|
TargetSHA: mergeOutput.BaseSHA.String(),
|
||||||
SourceSHA: mergeOutput.HeadSHA,
|
SourceSHA: mergeOutput.HeadSHA.String(),
|
||||||
}
|
}
|
||||||
if _, errAct := c.activityStore.CreateWithPayload(ctx, pr, session.Principal.ID, activityPayload); errAct != nil {
|
if _, errAct := c.activityStore.CreateWithPayload(ctx, pr, session.Principal.ID, activityPayload); errAct != nil {
|
||||||
// non-critical error
|
// non-critical error
|
||||||
@ -433,9 +435,9 @@ func (c *Controller) Merge(
|
|||||||
c.eventReporter.Merged(ctx, &pullreqevents.MergedPayload{
|
c.eventReporter.Merged(ctx, &pullreqevents.MergedPayload{
|
||||||
Base: eventBase(pr, &session.Principal),
|
Base: eventBase(pr, &session.Principal),
|
||||||
MergeMethod: in.Method,
|
MergeMethod: in.Method,
|
||||||
MergeSHA: mergeOutput.MergeSHA,
|
MergeSHA: mergeOutput.MergeSHA.String(),
|
||||||
TargetSHA: mergeOutput.BaseSHA,
|
TargetSHA: mergeOutput.BaseSHA.String(),
|
||||||
SourceSHA: mergeOutput.HeadSHA,
|
SourceSHA: mergeOutput.HeadSHA.String(),
|
||||||
})
|
})
|
||||||
|
|
||||||
var branchDeleted bool
|
var branchDeleted bool
|
||||||
@ -467,7 +469,7 @@ func (c *Controller) Merge(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &types.MergeResponse{
|
return &types.MergeResponse{
|
||||||
SHA: mergeOutput.MergeSHA,
|
SHA: mergeOutput.MergeSHA.String(),
|
||||||
BranchDeleted: branchDeleted,
|
BranchDeleted: branchDeleted,
|
||||||
RuleViolations: violations,
|
RuleViolations: violations,
|
||||||
}, nil, nil
|
}, nil, nil
|
||||||
|
@ -95,7 +95,7 @@ func (c *Controller) Create(
|
|||||||
|
|
||||||
mergeBaseSHA := mergeBaseResult.MergeBaseSHA
|
mergeBaseSHA := mergeBaseResult.MergeBaseSHA
|
||||||
|
|
||||||
if mergeBaseSHA == sourceSHA {
|
if mergeBaseSHA.String() == sourceSHA {
|
||||||
return nil, usererror.BadRequest("The source branch doesn't contain any new commits")
|
return nil, usererror.BadRequest("The source branch doesn't contain any new commits")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ func (c *Controller) Create(
|
|||||||
return nil, fmt.Errorf("failed to acquire PullReqSeq number: %w", err)
|
return nil, fmt.Errorf("failed to acquire PullReqSeq number: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pr := newPullReq(session, targetRepo.PullReqSeq, sourceRepo, targetRepo, in, sourceSHA, mergeBaseSHA)
|
pr := newPullReq(session, targetRepo.PullReqSeq, sourceRepo, targetRepo, in, sourceSHA, mergeBaseSHA.String())
|
||||||
|
|
||||||
err = c.pullreqStore.Create(ctx, pr)
|
err = c.pullreqStore.Create(ctx, pr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
|
|
||||||
"github.com/harness/gitness/app/auth"
|
"github.com/harness/gitness/app/auth"
|
||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
gittypes "github.com/harness/gitness/git/types"
|
gittypes "github.com/harness/gitness/git/api"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
)
|
)
|
||||||
|
@ -129,7 +129,7 @@ func (c *Controller) State(ctx context.Context,
|
|||||||
return nil, fmt.Errorf("failed to find merge base: %w", err)
|
return nil, fmt.Errorf("failed to find merge base: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeBaseSHA = mergeBaseResult.MergeBaseSHA
|
mergeBaseSHA = mergeBaseResult.MergeBaseSHA.String()
|
||||||
|
|
||||||
stateChange = changeReopen
|
stateChange = changeReopen
|
||||||
} else if pr.State == enum.PullReqStateOpen && in.State != enum.PullReqStateOpen {
|
} else if pr.State == enum.PullReqStateOpen && in.State != enum.PullReqStateOpen {
|
||||||
|
@ -82,7 +82,7 @@ func (c *Controller) ReviewSubmit(
|
|||||||
|
|
||||||
commit, err := c.git.GetCommit(ctx, &git.GetCommitParams{
|
commit, err := c.git.GetCommit(ctx, &git.GetCommitParams{
|
||||||
ReadParams: git.ReadParams{RepoUID: repo.GitUID},
|
ReadParams: git.ReadParams{RepoUID: repo.GitUID},
|
||||||
SHA: in.CommitSHA,
|
Revision: in.CommitSHA,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get git branch sha: %w", err)
|
return nil, fmt.Errorf("failed to get git branch sha: %w", err)
|
||||||
@ -101,7 +101,7 @@ func (c *Controller) ReviewSubmit(
|
|||||||
Updated: now,
|
Updated: now,
|
||||||
PullReqID: pr.ID,
|
PullReqID: pr.ID,
|
||||||
Decision: in.Decision,
|
Decision: in.Decision,
|
||||||
SHA: commitSHA,
|
SHA: commitSHA.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.reviewStore.Create(ctx, review)
|
err = c.reviewStore.Create(ctx, review)
|
||||||
@ -114,7 +114,7 @@ func (c *Controller) ReviewSubmit(
|
|||||||
ReviewerID: review.CreatedBy,
|
ReviewerID: review.CreatedBy,
|
||||||
})
|
})
|
||||||
|
|
||||||
_, err = c.updateReviewer(ctx, session, pr, review, commitSHA)
|
_, err = c.updateReviewer(ctx, session, pr, review, commitSHA.String())
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -127,7 +127,7 @@ func (c *Controller) ReviewSubmit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
payload := &types.PullRequestActivityPayloadReviewSubmit{
|
payload := &types.PullRequestActivityPayloadReviewSubmit{
|
||||||
CommitSHA: commitSHA,
|
CommitSHA: commitSHA.String(),
|
||||||
Decision: in.Decision,
|
Decision: in.Decision,
|
||||||
}
|
}
|
||||||
_, err = c.activityStore.CreateWithPayload(ctx, pr, session.Principal.ID, payload)
|
_, err = c.activityStore.CreateWithPayload(ctx, pr, session.Principal.ID, payload)
|
||||||
|
@ -158,7 +158,7 @@ func (c *Controller) CommitFiles(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
return types.CommitFilesResponse{
|
return types.CommitFilesResponse{
|
||||||
CommitID: commit.CommitID,
|
CommitID: commit.CommitID.String(),
|
||||||
RuleViolations: violations,
|
RuleViolations: violations,
|
||||||
}, nil, nil
|
}, nil, nil
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"github.com/harness/gitness/app/api/usererror"
|
"github.com/harness/gitness/app/api/usererror"
|
||||||
"github.com/harness/gitness/app/auth"
|
"github.com/harness/gitness/app/auth"
|
||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
gittypes "github.com/harness/gitness/git/types"
|
gittypes "github.com/harness/gitness/git/api"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
)
|
)
|
||||||
@ -58,7 +58,7 @@ func (c *Controller) CommitDiff(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
session *auth.Session,
|
session *auth.Session,
|
||||||
repoRef string,
|
repoRef string,
|
||||||
sha string,
|
rev string,
|
||||||
w io.Writer,
|
w io.Writer,
|
||||||
) error {
|
) error {
|
||||||
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
|
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
|
||||||
@ -68,7 +68,7 @@ func (c *Controller) CommitDiff(
|
|||||||
|
|
||||||
return c.git.CommitDiff(ctx, &git.GetCommitParams{
|
return c.git.CommitDiff(ctx, &git.GetCommitParams{
|
||||||
ReadParams: git.CreateReadParams(repo),
|
ReadParams: git.CreateReadParams(repo),
|
||||||
SHA: sha,
|
Revision: rev,
|
||||||
}, w)
|
}, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ func (c *Controller) GetCommit(ctx context.Context,
|
|||||||
|
|
||||||
rpcOut, err := c.git.GetCommit(ctx, &git.GetCommitParams{
|
rpcOut, err := c.git.GetCommit(ctx, &git.GetCommitParams{
|
||||||
ReadParams: git.CreateReadParams(repo),
|
ReadParams: git.CreateReadParams(repo),
|
||||||
SHA: sha,
|
Revision: sha,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get commit: %w", err)
|
return nil, fmt.Errorf("failed to get commit: %w", err)
|
||||||
|
@ -106,7 +106,7 @@ func mapBranch(b git.Branch) (Branch, error) {
|
|||||||
}
|
}
|
||||||
return Branch{
|
return Branch{
|
||||||
Name: b.Name,
|
Name: b.Name,
|
||||||
SHA: b.SHA,
|
SHA: b.SHA.String(),
|
||||||
Commit: commit,
|
Commit: commit,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ func mapCommitTag(t git.CommitTag) (CommitTag, error) {
|
|||||||
|
|
||||||
return CommitTag{
|
return CommitTag{
|
||||||
Name: t.Name,
|
Name: t.Name,
|
||||||
SHA: t.SHA,
|
SHA: t.SHA.String(),
|
||||||
IsAnnotated: t.IsAnnotated,
|
IsAnnotated: t.IsAnnotated,
|
||||||
Title: t.Title,
|
Title: t.Title,
|
||||||
Message: t.Message,
|
Message: t.Message,
|
||||||
|
@ -31,7 +31,7 @@ func (c *Controller) Raw(ctx context.Context,
|
|||||||
session *auth.Session,
|
session *auth.Session,
|
||||||
repoRef string,
|
repoRef string,
|
||||||
gitRef string,
|
gitRef string,
|
||||||
repoPath string,
|
path string,
|
||||||
) (io.ReadCloser, int64, error) {
|
) (io.ReadCloser, int64, error) {
|
||||||
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
|
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -48,7 +48,7 @@ func (c *Controller) Raw(ctx context.Context,
|
|||||||
treeNodeOutput, err := c.git.GetTreeNode(ctx, &git.GetTreeNodeParams{
|
treeNodeOutput, err := c.git.GetTreeNode(ctx, &git.GetTreeNodeParams{
|
||||||
ReadParams: readParams,
|
ReadParams: readParams,
|
||||||
GitREF: gitRef,
|
GitREF: gitRef,
|
||||||
Path: repoPath,
|
Path: path,
|
||||||
IncludeLatestCommit: false,
|
IncludeLatestCommit: false,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -59,7 +59,7 @@ func (c *Controller) Raw(ctx context.Context,
|
|||||||
if treeNodeOutput.Node.Type != git.TreeNodeTypeBlob {
|
if treeNodeOutput.Node.Type != git.TreeNodeTypeBlob {
|
||||||
return nil, 0, usererror.BadRequestf(
|
return nil, 0, usererror.BadRequestf(
|
||||||
"Object in '%s' at '/%s' is of type '%s'. Only objects of type %s support raw viewing.",
|
"Object in '%s' at '/%s' is of type '%s'. Only objects of type %s support raw viewing.",
|
||||||
gitRef, repoPath, treeNodeOutput.Node.Type, git.TreeNodeTypeBlob)
|
gitRef, path, treeNodeOutput.Node.Type, git.TreeNodeTypeBlob)
|
||||||
}
|
}
|
||||||
|
|
||||||
blobReader, err := c.git.GetBlob(ctx, &git.GetBlobParams{
|
blobReader, err := c.git.GetBlob(ctx, &git.GetBlobParams{
|
||||||
|
@ -101,9 +101,14 @@ func MapCommit(c *git.Commit) (*types.Commit, error) {
|
|||||||
deletions += stat.Deletions
|
deletions += stat.Deletions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parentSHAs := make([]string, len(c.ParentSHAs))
|
||||||
|
for i, sha := range c.ParentSHAs {
|
||||||
|
parentSHAs[i] = sha.String()
|
||||||
|
}
|
||||||
|
|
||||||
return &types.Commit{
|
return &types.Commit{
|
||||||
SHA: c.SHA,
|
SHA: c.SHA.String(),
|
||||||
ParentSHAs: c.ParentSHAs,
|
ParentSHAs: parentSHAs,
|
||||||
Title: c.Title,
|
Title: c.Title,
|
||||||
Message: c.Message,
|
Message: c.Message,
|
||||||
Author: *author,
|
Author: *author,
|
||||||
@ -145,8 +150,8 @@ func MapRenameDetails(c *git.RenameDetails) *types.RenameDetails {
|
|||||||
return &types.RenameDetails{
|
return &types.RenameDetails{
|
||||||
OldPath: c.OldPath,
|
OldPath: c.OldPath,
|
||||||
NewPath: c.NewPath,
|
NewPath: c.NewPath,
|
||||||
CommitShaBefore: c.CommitShaBefore,
|
CommitShaBefore: c.CommitShaBefore.String(),
|
||||||
CommitShaAfter: c.CommitShaAfter,
|
CommitShaAfter: c.CommitShaAfter.String(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
"github.com/harness/gitness/app/api/render"
|
"github.com/harness/gitness/app/api/render"
|
||||||
"github.com/harness/gitness/app/api/request"
|
"github.com/harness/gitness/app/api/request"
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
gittypes "github.com/harness/gitness/git/types"
|
gittypes "github.com/harness/gitness/git/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandleDiff returns a http.HandlerFunc that returns diff.
|
// HandleDiff returns a http.HandlerFunc that returns diff.
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
"github.com/harness/gitness/app/api/render"
|
"github.com/harness/gitness/app/api/render"
|
||||||
"github.com/harness/gitness/app/api/request"
|
"github.com/harness/gitness/app/api/request"
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
gittypes "github.com/harness/gitness/git/types"
|
gittypes "github.com/harness/gitness/git/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandleDiff returns the diff between two commits, branches or tags.
|
// HandleDiff returns the diff between two commits, branches or tags.
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"github.com/harness/gitness/app/api/request"
|
"github.com/harness/gitness/app/api/request"
|
||||||
"github.com/harness/gitness/app/api/usererror"
|
"github.com/harness/gitness/app/api/usererror"
|
||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
gittypes "github.com/harness/gitness/git/types"
|
gittypes "github.com/harness/gitness/git/api"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ import (
|
|||||||
"github.com/harness/gitness/app/api/usererror"
|
"github.com/harness/gitness/app/api/usererror"
|
||||||
"github.com/harness/gitness/app/services/protection"
|
"github.com/harness/gitness/app/services/protection"
|
||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
gittypes "github.com/harness/gitness/git/types"
|
gittypes "github.com/harness/gitness/git/api"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/harness/gitness/app/api/usererror"
|
"github.com/harness/gitness/app/api/usererror"
|
||||||
gittypes "github.com/harness/gitness/git/types"
|
gittypes "github.com/harness/gitness/git/api"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
)
|
)
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetFileDiffRequestsFromQuery(t *testing.T) {
|
func TestGetFileDiffRequestsFromQuery(t *testing.T) {
|
||||||
@ -30,7 +30,7 @@ func TestGetFileDiffRequestsFromQuery(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
wantFiles types.FileDiffRequests
|
wantFiles api.FileDiffRequests
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "full range",
|
name: "full range",
|
||||||
@ -42,7 +42,7 @@ func TestGetFileDiffRequestsFromQuery(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantFiles: types.FileDiffRequests{
|
wantFiles: api.FileDiffRequests{
|
||||||
{
|
{
|
||||||
Path: "file.txt",
|
Path: "file.txt",
|
||||||
StartLine: 1,
|
StartLine: 1,
|
||||||
@ -60,7 +60,7 @@ func TestGetFileDiffRequestsFromQuery(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantFiles: types.FileDiffRequests{
|
wantFiles: api.FileDiffRequests{
|
||||||
{
|
{
|
||||||
Path: "file.txt",
|
Path: "file.txt",
|
||||||
StartLine: 1,
|
StartLine: 1,
|
||||||
@ -77,7 +77,7 @@ func TestGetFileDiffRequestsFromQuery(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantFiles: types.FileDiffRequests{
|
wantFiles: api.FileDiffRequests{
|
||||||
{
|
{
|
||||||
Path: "file.txt",
|
Path: "file.txt",
|
||||||
EndLine: 20,
|
EndLine: 20,
|
||||||
@ -94,7 +94,7 @@ func TestGetFileDiffRequestsFromQuery(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantFiles: types.FileDiffRequests{
|
wantFiles: api.FileDiffRequests{
|
||||||
{
|
{
|
||||||
Path: "file.txt",
|
Path: "file.txt",
|
||||||
EndLine: 20,
|
EndLine: 20,
|
||||||
@ -119,7 +119,7 @@ func TestGetFileDiffRequestsFromQuery(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantFiles: types.FileDiffRequests{
|
wantFiles: api.FileDiffRequests{
|
||||||
{
|
{
|
||||||
Path: "file.txt",
|
Path: "file.txt",
|
||||||
EndLine: 20,
|
EndLine: 20,
|
||||||
|
@ -24,7 +24,6 @@ import (
|
|||||||
"github.com/harness/gitness/app/services/webhook"
|
"github.com/harness/gitness/app/services/webhook"
|
||||||
"github.com/harness/gitness/blob"
|
"github.com/harness/gitness/blob"
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
gittypes "github.com/harness/gitness/git/types"
|
|
||||||
"github.com/harness/gitness/lock"
|
"github.com/harness/gitness/lock"
|
||||||
"github.com/harness/gitness/store"
|
"github.com/harness/gitness/store"
|
||||||
"github.com/harness/gitness/types/check"
|
"github.com/harness/gitness/types/check"
|
||||||
@ -40,7 +39,6 @@ func Translate(ctx context.Context, err error) *Error {
|
|||||||
maxBytesErr *http.MaxBytesError
|
maxBytesErr *http.MaxBytesError
|
||||||
codeOwnersTooLargeError *codeowners.TooLargeError
|
codeOwnersTooLargeError *codeowners.TooLargeError
|
||||||
lockError *lock.Error
|
lockError *lock.Error
|
||||||
pathNotFoundError *gittypes.PathNotFoundError
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// print original error for debugging purposes
|
// print original error for debugging purposes
|
||||||
@ -88,13 +86,6 @@ func Translate(ctx context.Context, err error) *Error {
|
|||||||
return RequestTooLargef("The request is too large. maximum allowed size is %d bytes", maxBytesErr.Limit)
|
return RequestTooLargef("The request is too large. maximum allowed size is %d bytes", maxBytesErr.Limit)
|
||||||
|
|
||||||
// git errors
|
// git errors
|
||||||
case errors.As(err, &pathNotFoundError):
|
|
||||||
return Newf(
|
|
||||||
http.StatusNotFound,
|
|
||||||
pathNotFoundError.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
// application errors
|
|
||||||
case errors.As(err, &appError):
|
case errors.As(err, &appError):
|
||||||
if appError.Err != nil {
|
if appError.Err != nil {
|
||||||
log.Ctx(ctx).Warn().Err(appError.Err).Msgf("Application error translation is omitting internal details.")
|
log.Ctx(ctx).Warn().Err(appError.Err).Msgf("Application error translation is omitting internal details.")
|
||||||
|
@ -57,14 +57,14 @@ func (f *service) FindRef(
|
|||||||
func (f *service) FindCommit(
|
func (f *service) FindCommit(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repo *types.Repository,
|
repo *types.Repository,
|
||||||
sha string,
|
rawSHA string,
|
||||||
) (*types.Commit, error) {
|
) (*types.Commit, error) {
|
||||||
readParams := git.ReadParams{
|
readParams := git.ReadParams{
|
||||||
RepoUID: repo.GitUID,
|
RepoUID: repo.GitUID,
|
||||||
}
|
}
|
||||||
commitOutput, err := f.git.GetCommit(ctx, &git.GetCommitParams{
|
commitOutput, err := f.git.GetCommit(ctx, &git.GetCommitParams{
|
||||||
ReadParams: readParams,
|
ReadParams: readParams,
|
||||||
SHA: sha,
|
Revision: rawSHA,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -25,7 +25,6 @@ import (
|
|||||||
"github.com/harness/gitness/app/store"
|
"github.com/harness/gitness/app/store"
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
gittypes "github.com/harness/gitness/git/types"
|
|
||||||
gitness_store "github.com/harness/gitness/store"
|
gitness_store "github.com/harness/gitness/store"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
@ -237,7 +236,7 @@ func (s *Service) getCodeOwnerFile(
|
|||||||
|
|
||||||
return &File{
|
return &File{
|
||||||
Content: string(content),
|
Content: string(content),
|
||||||
SHA: output.SHA,
|
SHA: output.SHA.String(),
|
||||||
TotalSize: output.Size,
|
TotalSize: output.Size,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -256,7 +255,7 @@ func (s *Service) getCodeOwnerFileNode(
|
|||||||
Path: path,
|
Path: path,
|
||||||
})
|
})
|
||||||
|
|
||||||
if gittypes.IsPathNotFoundError(err) {
|
if errors.IsNotFound(err) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -97,7 +97,7 @@ func (s *Service) triggerPREventOnBranchUpdate(ctx context.Context,
|
|||||||
|
|
||||||
pr.Edited = time.Now().UnixMilli()
|
pr.Edited = time.Now().UnixMilli()
|
||||||
pr.SourceSHA = event.Payload.NewSHA
|
pr.SourceSHA = event.Payload.NewSHA
|
||||||
pr.MergeBaseSHA = newMergeBase
|
pr.MergeBaseSHA = newMergeBase.String()
|
||||||
|
|
||||||
// reset merge-check fields for new run
|
// reset merge-check fields for new run
|
||||||
pr.MergeCheckStatus = enum.MergeCheckStatusUnchecked
|
pr.MergeCheckStatus = enum.MergeCheckStatusUnchecked
|
||||||
@ -137,7 +137,7 @@ func (s *Service) triggerPREventOnBranchUpdate(ctx context.Context,
|
|||||||
OldSHA: event.Payload.OldSHA,
|
OldSHA: event.Payload.OldSHA,
|
||||||
NewSHA: event.Payload.NewSHA,
|
NewSHA: event.Payload.NewSHA,
|
||||||
OldMergeBaseSHA: oldMergeBase,
|
OldMergeBaseSHA: oldMergeBase,
|
||||||
NewMergeBaseSHA: newMergeBase,
|
NewMergeBaseSHA: newMergeBase.String(),
|
||||||
Forced: event.Payload.Forced,
|
Forced: event.Payload.Forced,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/harness/gitness/events"
|
"github.com/harness/gitness/events"
|
||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
gitenum "github.com/harness/gitness/git/enum"
|
gitenum "github.com/harness/gitness/git/enum"
|
||||||
|
"github.com/harness/gitness/git/sha"
|
||||||
)
|
)
|
||||||
|
|
||||||
// createHeadRefOnCreated handles pull request Created events.
|
// createHeadRefOnCreated handles pull request Created events.
|
||||||
@ -46,8 +47,8 @@ func (s *Service) createHeadRefOnCreated(ctx context.Context,
|
|||||||
WriteParams: writeParams,
|
WriteParams: writeParams,
|
||||||
Name: strconv.Itoa(int(event.Payload.Number)),
|
Name: strconv.Itoa(int(event.Payload.Number)),
|
||||||
Type: gitenum.RefTypePullReqHead,
|
Type: gitenum.RefTypePullReqHead,
|
||||||
NewValue: event.Payload.SourceSHA,
|
NewValue: sha.Must(event.Payload.SourceSHA),
|
||||||
OldValue: "", // this is a new pull request, so we expect that the ref doesn't exist
|
OldValue: sha.None, // this is a new pull request, so we expect that the ref doesn't exist
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update PR head ref: %w", err)
|
return fmt.Errorf("failed to update PR head ref: %w", err)
|
||||||
@ -77,8 +78,8 @@ func (s *Service) updateHeadRefOnBranchUpdate(ctx context.Context,
|
|||||||
WriteParams: writeParams,
|
WriteParams: writeParams,
|
||||||
Name: strconv.Itoa(int(event.Payload.Number)),
|
Name: strconv.Itoa(int(event.Payload.Number)),
|
||||||
Type: gitenum.RefTypePullReqHead,
|
Type: gitenum.RefTypePullReqHead,
|
||||||
NewValue: event.Payload.NewSHA,
|
NewValue: sha.Must(event.Payload.NewSHA),
|
||||||
OldValue: event.Payload.OldSHA,
|
OldValue: sha.Must(event.Payload.OldSHA),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update PR head ref after new commit: %w", err)
|
return fmt.Errorf("failed to update PR head ref after new commit: %w", err)
|
||||||
@ -108,8 +109,8 @@ func (s *Service) updateHeadRefOnReopen(ctx context.Context,
|
|||||||
WriteParams: writeParams,
|
WriteParams: writeParams,
|
||||||
Name: strconv.Itoa(int(event.Payload.Number)),
|
Name: strconv.Itoa(int(event.Payload.Number)),
|
||||||
Type: gitenum.RefTypePullReqHead,
|
Type: gitenum.RefTypePullReqHead,
|
||||||
NewValue: event.Payload.SourceSHA,
|
NewValue: sha.Must(event.Payload.SourceSHA),
|
||||||
OldValue: "", // the request is re-opened, so anything can be the old value
|
OldValue: sha.None, // the request is re-opened, so anything can be the old value
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update PR head ref after pull request reopen: %w", err)
|
return fmt.Errorf("failed to update PR head ref after pull request reopen: %w", err)
|
||||||
|
@ -25,10 +25,12 @@ import (
|
|||||||
"github.com/harness/gitness/events"
|
"github.com/harness/gitness/events"
|
||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
gitenum "github.com/harness/gitness/git/enum"
|
gitenum "github.com/harness/gitness/git/enum"
|
||||||
|
"github.com/harness/gitness/git/sha"
|
||||||
"github.com/harness/gitness/pubsub"
|
"github.com/harness/gitness/pubsub"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
|
|
||||||
|
"github.com/gotidy/ptr"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -109,8 +111,8 @@ func (s *Service) deleteMergeRef(ctx context.Context, repoID int64, prNum int64)
|
|||||||
WriteParams: writeParams,
|
WriteParams: writeParams,
|
||||||
Name: strconv.Itoa(int(prNum)),
|
Name: strconv.Itoa(int(prNum)),
|
||||||
Type: gitenum.RefTypePullReqMerge,
|
Type: gitenum.RefTypePullReqMerge,
|
||||||
NewValue: "", // when NewValue is empty will delete the ref.
|
NewValue: sha.None, // when NewValue is empty will delete the ref.
|
||||||
OldValue: "", // we don't care about the old value
|
OldValue: sha.None, // we don't care about the old value
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to remove PR merge ref: %w", err)
|
return fmt.Errorf("failed to remove PR merge ref: %w", err)
|
||||||
@ -205,7 +207,7 @@ func (s *Service) updateMergeDataInner(
|
|||||||
HeadBranch: pr.SourceBranch,
|
HeadBranch: pr.SourceBranch,
|
||||||
RefType: gitenum.RefTypePullReqMerge,
|
RefType: gitenum.RefTypePullReqMerge,
|
||||||
RefName: strconv.Itoa(int(pr.Number)),
|
RefName: strconv.Itoa(int(pr.Number)),
|
||||||
HeadExpectedSHA: newSHA,
|
HeadExpectedSHA: sha.Must(newSHA),
|
||||||
Force: true,
|
Force: true,
|
||||||
|
|
||||||
// set committer date to ensure repeatability of merge commit across replicas
|
// set committer date to ensure repeatability of merge commit across replicas
|
||||||
@ -222,17 +224,17 @@ func (s *Service) updateMergeDataInner(
|
|||||||
return events.NewDiscardEventErrorf("PR SHA %s is newer than %s", pr.SourceSHA, newSHA)
|
return events.NewDiscardEventErrorf("PR SHA %s is newer than %s", pr.SourceSHA, newSHA)
|
||||||
}
|
}
|
||||||
|
|
||||||
if mergeOutput.MergeSHA == "" || len(mergeOutput.ConflictFiles) > 0 {
|
if mergeOutput.MergeSHA.IsEmpty() || len(mergeOutput.ConflictFiles) > 0 {
|
||||||
pr.MergeCheckStatus = enum.MergeCheckStatusConflict
|
pr.MergeCheckStatus = enum.MergeCheckStatusConflict
|
||||||
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA
|
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
|
||||||
pr.MergeTargetSHA = &mergeOutput.BaseSHA
|
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
|
||||||
pr.MergeSHA = nil
|
pr.MergeSHA = nil
|
||||||
pr.MergeConflicts = mergeOutput.ConflictFiles
|
pr.MergeConflicts = mergeOutput.ConflictFiles
|
||||||
} else {
|
} else {
|
||||||
pr.MergeCheckStatus = enum.MergeCheckStatusMergeable
|
pr.MergeCheckStatus = enum.MergeCheckStatusMergeable
|
||||||
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA
|
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
|
||||||
pr.MergeTargetSHA = &mergeOutput.BaseSHA
|
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
|
||||||
pr.MergeSHA = &mergeOutput.MergeSHA
|
pr.MergeSHA = ptr.String(mergeOutput.MergeSHA.String())
|
||||||
pr.MergeConflicts = nil
|
pr.MergeConflicts = nil
|
||||||
}
|
}
|
||||||
pr.Stats.DiffStats = types.NewDiffStats(mergeOutput.CommitCount, mergeOutput.ChangedFileCount)
|
pr.Stats.DiffStats = types.NewDiffStats(mergeOutput.CommitCount, mergeOutput.ChangedFileCount)
|
||||||
|
@ -151,22 +151,22 @@ func (s *Service) handleEventBranchDeleted(ctx context.Context,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) fetchCommitInfoForEvent(ctx context.Context, repoUID string, sha string) (CommitInfo, error) {
|
func (s *Service) fetchCommitInfoForEvent(ctx context.Context, repoUID string, commitSHA string) (CommitInfo, error) {
|
||||||
out, err := s.git.GetCommit(ctx, &git.GetCommitParams{
|
out, err := s.git.GetCommit(ctx, &git.GetCommitParams{
|
||||||
ReadParams: git.ReadParams{
|
ReadParams: git.ReadParams{
|
||||||
RepoUID: repoUID,
|
RepoUID: repoUID,
|
||||||
},
|
},
|
||||||
SHA: sha,
|
Revision: commitSHA,
|
||||||
})
|
})
|
||||||
|
|
||||||
if errors.AsStatus(err) == errors.StatusNotFound {
|
if errors.AsStatus(err) == errors.StatusNotFound {
|
||||||
// this could happen if the commit has been deleted and garbage collected by now
|
// this could happen if the commit has been deleted and garbage collected by now
|
||||||
// or if the targetSha doesn't point to an event - either way discard the event.
|
// or if the targetSha doesn't point to an event - either way discard the event.
|
||||||
return CommitInfo{}, events.NewDiscardEventErrorf("commit with targetSha '%s' doesn't exist", sha)
|
return CommitInfo{}, events.NewDiscardEventErrorf("commit with targetSha '%s' doesn't exist", commitSHA)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CommitInfo{}, fmt.Errorf("failed to get commit with targetSha '%s': %w", sha, err)
|
return CommitInfo{}, fmt.Errorf("failed to get commit with targetSha '%s': %w", commitSHA, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return commitInfoFrom(out.Commit), nil
|
return commitInfoFrom(out.Commit), nil
|
||||||
|
@ -208,7 +208,7 @@ func commitInfoFrom(commit git.Commit) CommitInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return CommitInfo{
|
return CommitInfo{
|
||||||
SHA: commit.SHA,
|
SHA: commit.SHA.String(),
|
||||||
Message: commit.Message,
|
Message: commit.Message,
|
||||||
Author: signatureInfoFrom(commit.Author),
|
Author: signatureInfoFrom(commit.Author),
|
||||||
Committer: signatureInfoFrom(commit.Committer),
|
Committer: signatureInfoFrom(commit.Committer),
|
||||||
|
@ -78,7 +78,7 @@ import (
|
|||||||
"github.com/harness/gitness/encrypt"
|
"github.com/harness/gitness/encrypt"
|
||||||
"github.com/harness/gitness/events"
|
"github.com/harness/gitness/events"
|
||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
"github.com/harness/gitness/git/adapter"
|
"github.com/harness/gitness/git/api"
|
||||||
"github.com/harness/gitness/git/storage"
|
"github.com/harness/gitness/git/storage"
|
||||||
"github.com/harness/gitness/job"
|
"github.com/harness/gitness/job"
|
||||||
"github.com/harness/gitness/livelog"
|
"github.com/harness/gitness/livelog"
|
||||||
@ -126,7 +126,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e
|
|||||||
pullreqevents.WireSet,
|
pullreqevents.WireSet,
|
||||||
repoevents.WireSet,
|
repoevents.WireSet,
|
||||||
storage.WireSet,
|
storage.WireSet,
|
||||||
adapter.WireSet,
|
api.WireSet,
|
||||||
cliserver.ProvideGitConfig,
|
cliserver.ProvideGitConfig,
|
||||||
git.WireSet,
|
git.WireSet,
|
||||||
store.WireSet,
|
store.WireSet,
|
||||||
|
@ -77,7 +77,7 @@ import (
|
|||||||
"github.com/harness/gitness/encrypt"
|
"github.com/harness/gitness/encrypt"
|
||||||
"github.com/harness/gitness/events"
|
"github.com/harness/gitness/events"
|
||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
"github.com/harness/gitness/git/adapter"
|
"github.com/harness/gitness/git/api"
|
||||||
"github.com/harness/gitness/git/storage"
|
"github.com/harness/gitness/git/storage"
|
||||||
"github.com/harness/gitness/job"
|
"github.com/harness/gitness/job"
|
||||||
"github.com/harness/gitness/livelog"
|
"github.com/harness/gitness/livelog"
|
||||||
@ -135,17 +135,17 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cacheCache, err := adapter.ProvideLastCommitCache(typesConfig, universalClient)
|
cacheCache, err := api.ProvideLastCommitCache(typesConfig, universalClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
clientFactory := githook.ProvideFactory()
|
clientFactory := githook.ProvideFactory()
|
||||||
gitAdapter, err := git.ProvideGITAdapter(typesConfig, cacheCache, clientFactory)
|
apiGit, err := git.ProvideGITAdapter(typesConfig, cacheCache, clientFactory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
storageStore := storage.ProvideLocalStore()
|
storageStore := storage.ProvideLocalStore()
|
||||||
gitInterface, err := git.ProvideService(typesConfig, gitAdapter, storageStore)
|
gitInterface, err := git.ProvideService(typesConfig, apiGit, storageStore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
155
git/adapter.go
155
git/adapter.go
@ -1,155 +0,0 @@
|
|||||||
// Copyright 2023 Harness, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/harness/gitness/git/adapter"
|
|
||||||
"github.com/harness/gitness/git/enum"
|
|
||||||
"github.com/harness/gitness/git/types"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ Adapter = (*adapter.Adapter)(nil)
|
|
||||||
|
|
||||||
// Adapter for accessing git commands from gitea.
|
|
||||||
type Adapter interface {
|
|
||||||
InitRepository(ctx context.Context, path string, bare bool) error
|
|
||||||
OpenRepository(ctx context.Context, path string) (*git.Repository, error)
|
|
||||||
SharedRepository(tmp string, repoUID string, remotePath string) (*adapter.SharedRepo, error)
|
|
||||||
Config(ctx context.Context, repoPath, key, value string) error
|
|
||||||
CountObjects(ctx context.Context, repoPath string) (types.ObjectCount, error)
|
|
||||||
SetDefaultBranch(ctx context.Context, repoPath string,
|
|
||||||
defaultBranch string, allowEmpty bool) error
|
|
||||||
GetDefaultBranch(ctx context.Context, repoPath string) (string, error)
|
|
||||||
GetRemoteDefaultBranch(ctx context.Context,
|
|
||||||
remoteURL string) (string, error)
|
|
||||||
HasBranches(ctx context.Context, repoPath string) (bool, error)
|
|
||||||
|
|
||||||
Clone(ctx context.Context, from, to string, opts types.CloneRepoOptions) error
|
|
||||||
AddFiles(repoPath string, all bool, files ...string) error
|
|
||||||
Commit(ctx context.Context, repoPath string, opts types.CommitChangesOptions) error
|
|
||||||
Push(ctx context.Context, repoPath string, opts types.PushOptions) error
|
|
||||||
ReadTree(ctx context.Context, repoPath, ref string, w io.Writer, args ...string) error
|
|
||||||
GetTreeNode(ctx context.Context, repoPath string, ref string, treePath string) (*types.TreeNode, error)
|
|
||||||
ListTreeNodes(ctx context.Context, repoPath string, ref string, treePath string) ([]types.TreeNode, error)
|
|
||||||
PathsDetails(ctx context.Context, repoPath string, ref string, paths []string) ([]types.PathDetails, error)
|
|
||||||
GetSubmodule(ctx context.Context, repoPath string, ref string, treePath string) (*types.Submodule, error)
|
|
||||||
GetBlob(ctx context.Context, repoPath string, sha string, sizeLimit int64) (*types.BlobReader, error)
|
|
||||||
WalkReferences(ctx context.Context, repoPath string, handler types.WalkReferencesHandler,
|
|
||||||
opts *types.WalkReferencesOptions) error
|
|
||||||
GetCommit(ctx context.Context, repoPath string, ref string) (*types.Commit, error)
|
|
||||||
GetCommits(ctx context.Context, repoPath string, refs []string) ([]types.Commit, error)
|
|
||||||
ListCommits(
|
|
||||||
ctx context.Context,
|
|
||||||
repoPath string,
|
|
||||||
ref string,
|
|
||||||
page int,
|
|
||||||
limit int,
|
|
||||||
includeStats bool,
|
|
||||||
filter types.CommitFilter) ([]types.Commit, []types.PathRenameDetails, error)
|
|
||||||
ListCommitSHAs(ctx context.Context, repoPath string,
|
|
||||||
ref string, page int, limit int, filter types.CommitFilter) ([]string, error)
|
|
||||||
GetLatestCommit(ctx context.Context, repoPath string, ref string, treePath string) (*types.Commit, error)
|
|
||||||
GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error)
|
|
||||||
GetAnnotatedTag(ctx context.Context, repoPath string, sha string) (*types.Tag, error)
|
|
||||||
GetAnnotatedTags(ctx context.Context, repoPath string, shas []string) ([]types.Tag, error)
|
|
||||||
CreateTag(ctx context.Context, repoPath string, name string, targetSHA string, opts *types.CreateTagOptions) error
|
|
||||||
GetBranch(ctx context.Context, repoPath string, branchName string) (*types.Branch, error)
|
|
||||||
GetCommitDivergences(ctx context.Context, repoPath string,
|
|
||||||
requests []types.CommitDivergenceRequest, max int32) ([]types.CommitDivergence, error)
|
|
||||||
GetRef(ctx context.Context, repoPath string, reference string) (string, error)
|
|
||||||
UpdateRef(ctx context.Context, envVars map[string]string, repoPath, reference, newValue, oldValue string) error
|
|
||||||
CreateTemporaryRepoForPR(ctx context.Context, reposTempPath string, pr *types.PullRequest,
|
|
||||||
baseBranch, trackingBranch string) (types.TempRepository, error)
|
|
||||||
Merge(ctx context.Context, pr *types.PullRequest, mergeMethod enum.MergeMethod, baseBranch, trackingBranch string,
|
|
||||||
tmpBasePath string, mergeMsg string, identity *types.Identity, env ...string) (types.MergeResult, error)
|
|
||||||
GetMergeBase(ctx context.Context, repoPath, remote, base, head string) (string, string, error)
|
|
||||||
IsAncestor(ctx context.Context, repoPath, ancestorCommitSHA, descendantCommitSHA string) (bool, error)
|
|
||||||
Blame(ctx context.Context, repoPath, rev, file string, lineFrom, lineTo int) types.BlameReader
|
|
||||||
Sync(ctx context.Context, repoPath string, source string, refSpecs []string) error
|
|
||||||
|
|
||||||
//
|
|
||||||
// Diff operations
|
|
||||||
//
|
|
||||||
|
|
||||||
GetDiffTree(ctx context.Context,
|
|
||||||
repoPath,
|
|
||||||
baseBranch,
|
|
||||||
headBranch string) (string, error)
|
|
||||||
|
|
||||||
RawDiff(ctx context.Context,
|
|
||||||
w io.Writer,
|
|
||||||
repoPath,
|
|
||||||
base,
|
|
||||||
head string,
|
|
||||||
mergeBase bool,
|
|
||||||
paths ...types.FileDiffRequest) error
|
|
||||||
|
|
||||||
CommitDiff(ctx context.Context,
|
|
||||||
repoPath,
|
|
||||||
sha string,
|
|
||||||
w io.Writer) error
|
|
||||||
|
|
||||||
DiffShortStat(ctx context.Context,
|
|
||||||
repoPath string,
|
|
||||||
baseRef string,
|
|
||||||
headRef string,
|
|
||||||
useMergeBase bool) (types.DiffShortStat, error)
|
|
||||||
|
|
||||||
GetDiffHunkHeaders(ctx context.Context,
|
|
||||||
repoPath string,
|
|
||||||
targetRef string,
|
|
||||||
sourceRef string) ([]*types.DiffFileHunkHeaders, error)
|
|
||||||
|
|
||||||
DiffCut(ctx context.Context,
|
|
||||||
repoPath string,
|
|
||||||
targetRef string,
|
|
||||||
sourceRef string,
|
|
||||||
path string,
|
|
||||||
params types.DiffCutParams) (types.HunkHeader, types.Hunk, error)
|
|
||||||
|
|
||||||
MatchFiles(ctx context.Context,
|
|
||||||
repoPath string,
|
|
||||||
ref string,
|
|
||||||
dirPath string,
|
|
||||||
regExpDef string,
|
|
||||||
maxSize int) ([]types.FileContent, error)
|
|
||||||
|
|
||||||
// http
|
|
||||||
InfoRefs(
|
|
||||||
ctx context.Context,
|
|
||||||
repoPath string,
|
|
||||||
service string,
|
|
||||||
w io.Writer,
|
|
||||||
env ...string,
|
|
||||||
) error
|
|
||||||
ServicePack(
|
|
||||||
ctx context.Context,
|
|
||||||
repoPath string,
|
|
||||||
service string,
|
|
||||||
stdin io.Reader,
|
|
||||||
stdout io.Writer,
|
|
||||||
env ...string,
|
|
||||||
) error
|
|
||||||
DiffFileName(ctx context.Context,
|
|
||||||
repoPath string,
|
|
||||||
baseRef string,
|
|
||||||
headRef string,
|
|
||||||
mergeBase bool) ([]string, error)
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
// Copyright 2023 Harness, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package adapter_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBlameEmptyFile(t *testing.T) {
|
|
||||||
git := setupGit(t)
|
|
||||||
repo, teardown := setupRepo(t, git, "testblameemptyfile")
|
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
baseBranch := "main"
|
|
||||||
// write empty file to main branch
|
|
||||||
_, parentSHA := writeFile(t, repo, "file.txt", "", nil)
|
|
||||||
|
|
||||||
err := repo.SetReference("refs/heads/"+baseBranch, parentSHA.String())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed updating reference '%s': %v", baseBranch, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := git.Blame(context.Background(), repo.Path, "main", "file.txt", 0, 0)
|
|
||||||
|
|
||||||
part, err := reader.NextPart()
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Errorf("Blame reader should return empty string but got error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if part != nil {
|
|
||||||
t.Errorf("Blame reader should be nil but got: %v", part)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,153 +0,0 @@
|
|||||||
// Copyright 2023 Harness, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package adapter_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAdapter_RawDiff(t *testing.T) {
|
|
||||||
git := setupGit(t)
|
|
||||||
repo, teardown := setupRepo(t, git, "testrawdiff")
|
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
testFileName := "file.txt"
|
|
||||||
|
|
||||||
baseBranch := "main"
|
|
||||||
// write file to main branch
|
|
||||||
oid1, parentSHA := writeFile(t, repo, testFileName, "some content", nil)
|
|
||||||
|
|
||||||
err := repo.SetReference("refs/heads/"+baseBranch, parentSHA.String())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed updating reference '%s': %v", baseBranch, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
baseTag := "0.0.1"
|
|
||||||
err = repo.CreateAnnotatedTag(baseTag, "test tag 1", parentSHA.String())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error creating annotated tag '%s': %v", baseTag, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
headBranch := "dev"
|
|
||||||
|
|
||||||
// create branch
|
|
||||||
err = repo.CreateBranch(headBranch, baseBranch)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed creating a branch '%s': %v", headBranch, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// write file to main branch
|
|
||||||
oid2, sha := writeFile(t, repo, testFileName, "new content", []string{parentSHA.String()})
|
|
||||||
|
|
||||||
err = repo.SetReference("refs/heads/"+headBranch, sha.String())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed updating reference '%s': %v", headBranch, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
headTag := "0.0.2"
|
|
||||||
err = repo.CreateAnnotatedTag(headTag, "test tag 2", sha.String())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error creating annotated tag '%s': %v", headTag, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
want := `diff --git a/` + testFileName + ` b/` + testFileName + `
|
|
||||||
index ` + oid1.String() + `..` + oid2.String() + ` 100644
|
|
||||||
--- a/` + testFileName + `
|
|
||||||
+++ b/` + testFileName + `
|
|
||||||
@@ -1 +1 @@
|
|
||||||
-some content
|
|
||||||
\ No newline at end of file
|
|
||||||
+new content
|
|
||||||
\ No newline at end of file
|
|
||||||
`
|
|
||||||
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
repoPath string
|
|
||||||
baseRef string
|
|
||||||
headRef string
|
|
||||||
mergeBase bool
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
wantW string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "test branches",
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
repoPath: repo.Path,
|
|
||||||
baseRef: baseBranch,
|
|
||||||
headRef: headBranch,
|
|
||||||
mergeBase: false,
|
|
||||||
},
|
|
||||||
wantW: want,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test annotated tag",
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
repoPath: repo.Path,
|
|
||||||
baseRef: baseTag,
|
|
||||||
headRef: headTag,
|
|
||||||
mergeBase: false,
|
|
||||||
},
|
|
||||||
wantW: want,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test branches using merge-base",
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
repoPath: repo.Path,
|
|
||||||
baseRef: baseBranch,
|
|
||||||
headRef: headBranch,
|
|
||||||
mergeBase: true,
|
|
||||||
},
|
|
||||||
wantW: want,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test annotated tag using merge-base",
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
repoPath: repo.Path,
|
|
||||||
baseRef: baseTag,
|
|
||||||
headRef: headTag,
|
|
||||||
mergeBase: true,
|
|
||||||
},
|
|
||||||
wantW: want,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
w := &bytes.Buffer{}
|
|
||||||
err := git.RawDiff(tt.args.ctx, w, tt.args.repoPath, tt.args.baseRef, tt.args.headRef, tt.args.mergeBase)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("RawDiff() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if gotW := w.String(); gotW != tt.wantW {
|
|
||||||
t.Errorf("RawDiff() gotW = %v, want %v", gotW, tt.wantW)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,133 +0,0 @@
|
|||||||
// Copyright 2023 Harness, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
|
||||||
|
|
||||||
gitea "code.gitea.io/gitea/modules/git"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrRepositoryPathEmpty = errors.InvalidArgument("repository path cannot be empty")
|
|
||||||
ErrBranchNameEmpty = errors.InvalidArgument("branch name cannot be empty")
|
|
||||||
ErrParseDiffHunkHeader = errors.Internal(nil, "failed to parse diff hunk header")
|
|
||||||
)
|
|
||||||
|
|
||||||
type runStdError struct {
|
|
||||||
err error
|
|
||||||
stderr string
|
|
||||||
errMsg string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *runStdError) Error() string {
|
|
||||||
// the stderr must be in the returned error text, some code only checks `strings.Contains(err.Error(), "git error")`
|
|
||||||
if r.errMsg == "" {
|
|
||||||
r.errMsg = gitea.ConcatenateError(r.err, r.stderr).Error()
|
|
||||||
}
|
|
||||||
return r.errMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *runStdError) Unwrap() error {
|
|
||||||
return r.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *runStdError) Stderr() string {
|
|
||||||
return r.stderr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *runStdError) IsExitCode(code int) bool {
|
|
||||||
var exitError *exec.ExitError
|
|
||||||
if errors.As(r.err, &exitError) {
|
|
||||||
return exitError.ExitCode() == code
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logs the error and message, returns either the provided message or a git equivalent if possible.
|
|
||||||
// Always logs the full message with error as warning.
|
|
||||||
func processGiteaErrorf(err error, format string, args ...interface{}) error {
|
|
||||||
// create fallback error returned if we can't map it
|
|
||||||
fallbackErr := errors.Internal(err, format, args...)
|
|
||||||
|
|
||||||
// always log internal error together with message.
|
|
||||||
log.Warn().Msgf("%v: [GITEA] %v", fallbackErr, err)
|
|
||||||
|
|
||||||
// check if it's a RunStdError error (contains raw git error)
|
|
||||||
var runStdErr gitea.RunStdError
|
|
||||||
if errors.As(err, &runStdErr) {
|
|
||||||
return mapGiteaRunStdError(runStdErr, fallbackErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
// gitea is using errors.New(no such file or directory") exclusively for OpenRepository ... (at least as of now)
|
|
||||||
case err.Error() == "no such file or directory":
|
|
||||||
return errors.NotFound("repository not found")
|
|
||||||
case gitea.IsErrNotExist(err):
|
|
||||||
return errors.NotFound(format, args, err)
|
|
||||||
case gitea.IsErrBranchNotExist(err):
|
|
||||||
return errors.NotFound(format, args, err)
|
|
||||||
default:
|
|
||||||
return fallbackErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Improve gitea error handling.
|
|
||||||
// Doubt this will work for all std errors, as git doesn't seem to have nice error codes.
|
|
||||||
func mapGiteaRunStdError(err gitea.RunStdError, fallback error) error {
|
|
||||||
switch {
|
|
||||||
// exit status 128 - fatal: A branch named 'mybranch' already exists.
|
|
||||||
// exit status 128 - fatal: cannot lock ref 'refs/heads/a': 'refs/heads/a/b' exists; cannot create 'refs/heads/a'
|
|
||||||
case err.IsExitCode(128) && strings.Contains(err.Stderr(), "exists"):
|
|
||||||
return errors.Conflict(err.Stderr())
|
|
||||||
|
|
||||||
// exit status 128 - fatal: 'a/bc/d/' is not a valid branch name.
|
|
||||||
case err.IsExitCode(128) && strings.Contains(err.Stderr(), "not a valid"):
|
|
||||||
return errors.InvalidArgument(err.Stderr())
|
|
||||||
|
|
||||||
// exit status 1 - error: branch 'mybranch' not found.
|
|
||||||
case err.IsExitCode(1) && strings.Contains(err.Stderr(), "not found"):
|
|
||||||
return errors.NotFound(err.Stderr())
|
|
||||||
|
|
||||||
// exit status 128 - fatal: ambiguous argument 'branch1...branch2': unknown revision or path not in the working tree.
|
|
||||||
case err.IsExitCode(128) && strings.Contains(err.Stderr(), "unknown revision"):
|
|
||||||
msg := "unknown revision or path not in the working tree"
|
|
||||||
// parse the error response from git output
|
|
||||||
lines := strings.Split(err.Error(), "\n")
|
|
||||||
if len(lines) > 0 {
|
|
||||||
cols := strings.Split(lines[0], ": ")
|
|
||||||
if len(cols) >= 2 {
|
|
||||||
msg = cols[1] + ", " + cols[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.NotFound(msg)
|
|
||||||
|
|
||||||
// exit status 128 - fatal: couldn't find remote ref v1.
|
|
||||||
case err.IsExitCode(128) && strings.Contains(err.Stderr(), "couldn't find"):
|
|
||||||
return errors.NotFound(err.Stderr())
|
|
||||||
|
|
||||||
// exit status 128 - fatal: unable to access 'http://127.0.0.1:4101/hvfl1xj5fojwlrw77xjflw80uxjous254jrr967rvj/':
|
|
||||||
// Failed to connect to 127.0.0.1 port 4101 after 4 ms: Connection refused
|
|
||||||
case err.IsExitCode(128) && strings.Contains(err.Stderr(), "Failed to connect"):
|
|
||||||
return errors.Internal(err, "failed to connect")
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
// Copyright 2023 Harness, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/harness/gitness/git/types"
|
|
||||||
|
|
||||||
gitea "code.gitea.io/gitea/modules/git"
|
|
||||||
)
|
|
||||||
|
|
||||||
func mapGiteaRawRef(
|
|
||||||
raw map[string]string,
|
|
||||||
) (map[types.GitReferenceField]string, error) {
|
|
||||||
res := make(map[types.GitReferenceField]string, len(raw))
|
|
||||||
for k, v := range raw {
|
|
||||||
gitRefField, err := types.ParseGitReferenceField(k)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res[gitRefField] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapToGiteaReferenceSortingArgument(
|
|
||||||
s types.GitReferenceField,
|
|
||||||
o types.SortOrder,
|
|
||||||
) string {
|
|
||||||
sortBy := string(types.GitReferenceFieldRefName)
|
|
||||||
desc := o == types.SortOrderDesc
|
|
||||||
|
|
||||||
if s == types.GitReferenceFieldCreatorDate {
|
|
||||||
sortBy = string(types.GitReferenceFieldCreatorDate)
|
|
||||||
if o == types.SortOrderDefault {
|
|
||||||
desc = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if desc {
|
|
||||||
return "-" + sortBy
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortBy
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapGiteaCommit(giteaCommit *gitea.Commit) (*types.Commit, error) {
|
|
||||||
if giteaCommit == nil {
|
|
||||||
return nil, fmt.Errorf("gitea commit is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
author, err := mapGiteaSignature(giteaCommit.Author)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to map gitea author: %w", err)
|
|
||||||
}
|
|
||||||
committer, err := mapGiteaSignature(giteaCommit.Committer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to map gitea commiter: %w", err)
|
|
||||||
}
|
|
||||||
parentShas := make([]string, len(giteaCommit.Parents))
|
|
||||||
for i := range giteaCommit.Parents {
|
|
||||||
parentShas[i] = giteaCommit.Parents[i].String()
|
|
||||||
}
|
|
||||||
return &types.Commit{
|
|
||||||
SHA: giteaCommit.ID.String(),
|
|
||||||
ParentSHAs: parentShas,
|
|
||||||
Title: giteaCommit.Summary(),
|
|
||||||
// remove potential tailing newlines from message
|
|
||||||
Message: strings.TrimRight(giteaCommit.Message(), "\n"),
|
|
||||||
Author: author,
|
|
||||||
Committer: committer,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapGiteaSignature(
|
|
||||||
giteaSignature *gitea.Signature,
|
|
||||||
) (types.Signature, error) {
|
|
||||||
if giteaSignature == nil {
|
|
||||||
return types.Signature{}, fmt.Errorf("gitea signature is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
return types.Signature{
|
|
||||||
Identity: types.Identity{
|
|
||||||
Name: giteaSignature.Name,
|
|
||||||
Email: giteaSignature.Email,
|
|
||||||
},
|
|
||||||
When: giteaSignature.When,
|
|
||||||
}, nil
|
|
||||||
}
|
|
@ -1,635 +0,0 @@
|
|||||||
// Copyright 2023 Harness, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/harness/gitness/git/enum"
|
|
||||||
"github.com/harness/gitness/git/tempdir"
|
|
||||||
"github.com/harness/gitness/git/types"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateTemporaryRepo creates a temporary repo with "base" for pr.BaseBranch and "tracking" for pr.HeadBranch
|
|
||||||
// it also create a second base branch called "original_base".
|
|
||||||
//
|
|
||||||
//nolint:funlen,gocognit // need refactor
|
|
||||||
func (a Adapter) CreateTemporaryRepoForPR(
|
|
||||||
ctx context.Context,
|
|
||||||
reposTempPath string,
|
|
||||||
pr *types.PullRequest,
|
|
||||||
baseBranch string,
|
|
||||||
trackingBranch string,
|
|
||||||
) (types.TempRepository, error) {
|
|
||||||
if pr.BaseRepoPath == "" && pr.HeadRepoPath != "" {
|
|
||||||
pr.BaseRepoPath = pr.HeadRepoPath
|
|
||||||
}
|
|
||||||
|
|
||||||
if pr.HeadRepoPath == "" && pr.BaseRepoPath != "" {
|
|
||||||
pr.HeadRepoPath = pr.BaseRepoPath
|
|
||||||
}
|
|
||||||
|
|
||||||
if pr.BaseBranch == "" {
|
|
||||||
return types.TempRepository{}, errors.New("empty base branch")
|
|
||||||
}
|
|
||||||
|
|
||||||
if pr.HeadBranch == "" {
|
|
||||||
return types.TempRepository{}, errors.New("empty head branch")
|
|
||||||
}
|
|
||||||
|
|
||||||
baseRepoPath := pr.BaseRepoPath
|
|
||||||
headRepoPath := pr.HeadRepoPath
|
|
||||||
|
|
||||||
// Clone base repo.
|
|
||||||
tmpBasePath, err := tempdir.CreateTemporaryPath(reposTempPath, "pull")
|
|
||||||
if err != nil {
|
|
||||||
return types.TempRepository{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = a.InitRepository(ctx, tmpBasePath, false); err != nil {
|
|
||||||
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
|
|
||||||
return types.TempRepository{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteRepoName := "head_repo"
|
|
||||||
|
|
||||||
// Add head repo remote.
|
|
||||||
addCacheRepo := func(staging, cache string) error {
|
|
||||||
var f *os.File
|
|
||||||
alternates := filepath.Join(staging, ".git", "objects", "info", "alternates")
|
|
||||||
f, err = os.OpenFile(alternates, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open alternates file '%s': %w", alternates, err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
data := filepath.Join(cache, "objects")
|
|
||||||
if _, err = fmt.Fprintln(f, data); err != nil {
|
|
||||||
return fmt.Errorf("failed to write alternates file '%s': %w", alternates, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = addCacheRepo(tmpBasePath, baseRepoPath); err != nil {
|
|
||||||
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
|
|
||||||
return types.TempRepository{},
|
|
||||||
fmt.Errorf("unable to add base repository to temporary repo [%s -> tmpBasePath]: %w", pr.BaseRepoPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var outbuf, errbuf strings.Builder
|
|
||||||
if err = git.NewCommand(ctx, "remote", "add", "-t", pr.BaseBranch, "-m", pr.BaseBranch, "origin", baseRepoPath).
|
|
||||||
Run(&git.RunOpts{
|
|
||||||
Dir: tmpBasePath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
}); err != nil {
|
|
||||||
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
|
|
||||||
giteaErr := &giteaRunStdError{err: err, stderr: errbuf.String()}
|
|
||||||
return types.TempRepository{}, processGiteaErrorf(giteaErr, "unable to add base repository as origin "+
|
|
||||||
"[%s -> tmpBasePath]:\n%s\n%s", pr.BaseRepoPath, outbuf.String(), errbuf.String())
|
|
||||||
}
|
|
||||||
outbuf.Reset()
|
|
||||||
errbuf.Reset()
|
|
||||||
|
|
||||||
// Fetch base branch
|
|
||||||
baseCommit, err := a.GetCommit(ctx, pr.BaseRepoPath, pr.BaseBranch)
|
|
||||||
if err != nil {
|
|
||||||
return types.TempRepository{}, fmt.Errorf("failed to get commit of base branch '%s', error: %w", pr.BaseBranch, err)
|
|
||||||
}
|
|
||||||
baseID := baseCommit.SHA
|
|
||||||
if err = git.NewCommand(ctx, "fetch", "origin", "--no-tags", "--",
|
|
||||||
baseID+":"+baseBranch, baseID+":original_"+baseBranch).
|
|
||||||
Run(&git.RunOpts{
|
|
||||||
Dir: tmpBasePath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
}); err != nil {
|
|
||||||
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
|
|
||||||
giteaErr := &giteaRunStdError{err: err, stderr: errbuf.String()}
|
|
||||||
return types.TempRepository{}, processGiteaErrorf(giteaErr, "unable to fetch origin base branch "+
|
|
||||||
"[%s:%s -> base, original_base in tmpBasePath].\n%s\n%s",
|
|
||||||
pr.BaseRepoPath, pr.BaseBranch, outbuf.String(), errbuf.String())
|
|
||||||
}
|
|
||||||
outbuf.Reset()
|
|
||||||
errbuf.Reset()
|
|
||||||
|
|
||||||
if err = git.NewCommand(ctx, "symbolic-ref", "HEAD", git.BranchPrefix+baseBranch).
|
|
||||||
Run(&git.RunOpts{
|
|
||||||
Dir: tmpBasePath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
}); err != nil {
|
|
||||||
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
|
|
||||||
giteaErr := &giteaRunStdError{err: err, stderr: errbuf.String()}
|
|
||||||
return types.TempRepository{}, processGiteaErrorf(giteaErr, "unable to set HEAD as base "+
|
|
||||||
"branch [tmpBasePath]:\n%s\n%s", outbuf.String(), errbuf.String())
|
|
||||||
}
|
|
||||||
outbuf.Reset()
|
|
||||||
errbuf.Reset()
|
|
||||||
|
|
||||||
if err = addCacheRepo(tmpBasePath, headRepoPath); err != nil {
|
|
||||||
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
|
|
||||||
giteaErr := &giteaRunStdError{err: err, stderr: errbuf.String()}
|
|
||||||
return types.TempRepository{}, processGiteaErrorf(giteaErr, "unable to head base repository "+
|
|
||||||
"to temporary repo [%s -> tmpBasePath]", pr.HeadRepoPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = git.NewCommand(ctx, "remote", "add", remoteRepoName, headRepoPath).
|
|
||||||
Run(&git.RunOpts{
|
|
||||||
Dir: tmpBasePath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
}); err != nil {
|
|
||||||
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
|
|
||||||
giteaErr := &giteaRunStdError{err: err, stderr: errbuf.String()}
|
|
||||||
return types.TempRepository{}, processGiteaErrorf(giteaErr, "unable to add head repository as head_repo "+
|
|
||||||
"[%s -> tmpBasePath]:\n%s\n%s", pr.HeadRepoPath, outbuf.String(), errbuf.String())
|
|
||||||
}
|
|
||||||
outbuf.Reset()
|
|
||||||
errbuf.Reset()
|
|
||||||
|
|
||||||
headCommit, err := a.GetCommit(ctx, pr.HeadRepoPath, pr.HeadBranch)
|
|
||||||
if err != nil {
|
|
||||||
return types.TempRepository{}, fmt.Errorf("failed to get commit of head branch '%s', error: %w", pr.HeadBranch, err)
|
|
||||||
}
|
|
||||||
headID := headCommit.SHA
|
|
||||||
if err = git.NewCommand(ctx, "fetch", "--no-tags", remoteRepoName, headID+":"+trackingBranch).
|
|
||||||
Run(&git.RunOpts{
|
|
||||||
Dir: tmpBasePath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
}); err != nil {
|
|
||||||
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
|
|
||||||
giteaErr := &giteaRunStdError{err: err, stderr: errbuf.String()}
|
|
||||||
return types.TempRepository{}, processGiteaErrorf(giteaErr, "unable to fetch head_repo head branch "+
|
|
||||||
"[%s:%s -> tracking in tmpBasePath]:\n%s\n%s",
|
|
||||||
pr.HeadRepoPath, pr.HeadBranch, outbuf.String(), errbuf.String())
|
|
||||||
}
|
|
||||||
outbuf.Reset()
|
|
||||||
errbuf.Reset()
|
|
||||||
|
|
||||||
return types.TempRepository{
|
|
||||||
Path: tmpBasePath,
|
|
||||||
BaseSHA: baseID,
|
|
||||||
HeadSHA: headID,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type runMergeResult struct {
|
|
||||||
conflictFiles []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func runMergeCommand(
|
|
||||||
ctx context.Context,
|
|
||||||
pr *types.PullRequest,
|
|
||||||
mergeMethod enum.MergeMethod,
|
|
||||||
cmd *git.Command,
|
|
||||||
tmpBasePath string,
|
|
||||||
env []string,
|
|
||||||
) (runMergeResult, error) {
|
|
||||||
var outbuf, errbuf strings.Builder
|
|
||||||
if err := cmd.Run(&git.RunOpts{
|
|
||||||
Dir: tmpBasePath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
Env: env,
|
|
||||||
}); err != nil {
|
|
||||||
if strings.Contains(errbuf.String(), "refusing to merge unrelated histories") {
|
|
||||||
return runMergeResult{}, &types.MergeUnrelatedHistoriesError{
|
|
||||||
Method: mergeMethod,
|
|
||||||
StdOut: outbuf.String(),
|
|
||||||
StdErr: errbuf.String(),
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge will leave a MERGE_HEAD file in the .git folder if there is a conflict
|
|
||||||
if _, statErr := os.Stat(filepath.Join(tmpBasePath, ".git", "MERGE_HEAD")); statErr == nil {
|
|
||||||
// We have a merge conflict error
|
|
||||||
files, cferr := conflictFiles(ctx, pr, env, tmpBasePath)
|
|
||||||
if cferr != nil {
|
|
||||||
return runMergeResult{}, cferr
|
|
||||||
}
|
|
||||||
return runMergeResult{
|
|
||||||
conflictFiles: files,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
giteaErr := &giteaRunStdError{err: err, stderr: errbuf.String()}
|
|
||||||
return runMergeResult{}, processGiteaErrorf(giteaErr, "git merge [%s -> %s]\n%s\n%s",
|
|
||||||
pr.HeadBranch, pr.BaseBranch, outbuf.String(), errbuf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return runMergeResult{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func commitAndSignNoAuthor(
|
|
||||||
ctx context.Context,
|
|
||||||
pr *types.PullRequest,
|
|
||||||
message string,
|
|
||||||
signArg string,
|
|
||||||
tmpBasePath string,
|
|
||||||
env []string,
|
|
||||||
) error {
|
|
||||||
var outbuf, errbuf strings.Builder
|
|
||||||
if signArg == "" {
|
|
||||||
if err := git.NewCommand(ctx, "commit", "-m", message).
|
|
||||||
Run(&git.RunOpts{
|
|
||||||
Env: env,
|
|
||||||
Dir: tmpBasePath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
}); err != nil {
|
|
||||||
return processGiteaErrorf(err, "git commit [%s -> %s]\n%s\n%s",
|
|
||||||
pr.HeadBranch, pr.BaseBranch, outbuf.String(), errbuf.String())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := git.NewCommand(ctx, "commit", signArg, "-m", message).
|
|
||||||
Run(&git.RunOpts{
|
|
||||||
Env: env,
|
|
||||||
Dir: tmpBasePath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
}); err != nil {
|
|
||||||
return processGiteaErrorf(err, "git commit [%s -> %s]\n%s\n%s",
|
|
||||||
pr.HeadBranch, pr.BaseBranch, outbuf.String(), errbuf.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge merges changes between 2 refs (branch, commits or tags).
|
|
||||||
//
|
|
||||||
//nolint:gocognit,nestif
|
|
||||||
func (a Adapter) Merge(
|
|
||||||
ctx context.Context,
|
|
||||||
pr *types.PullRequest,
|
|
||||||
mergeMethod enum.MergeMethod,
|
|
||||||
baseBranch string,
|
|
||||||
trackingBranch string,
|
|
||||||
tmpBasePath string,
|
|
||||||
mergeMsg string,
|
|
||||||
identity *types.Identity,
|
|
||||||
env ...string,
|
|
||||||
) (types.MergeResult, error) {
|
|
||||||
var (
|
|
||||||
outbuf, errbuf strings.Builder
|
|
||||||
)
|
|
||||||
|
|
||||||
if mergeMsg == "" {
|
|
||||||
mergeMsg = "Merge commit"
|
|
||||||
}
|
|
||||||
|
|
||||||
stagingBranch := "staging"
|
|
||||||
// TODO: sign merge commit
|
|
||||||
signArg := "--no-gpg-sign"
|
|
||||||
|
|
||||||
switch mergeMethod {
|
|
||||||
case enum.MergeMethodMerge:
|
|
||||||
cmd := git.NewCommand(ctx, "merge", "--no-ff", "--no-commit", trackingBranch)
|
|
||||||
result, err := runMergeCommand(ctx, pr, mergeMethod, cmd, tmpBasePath, env)
|
|
||||||
if err != nil {
|
|
||||||
return types.MergeResult{}, fmt.Errorf("unable to merge tracking into base: %w", err)
|
|
||||||
}
|
|
||||||
if len(result.conflictFiles) > 0 {
|
|
||||||
return types.MergeResult{ConflictFiles: result.conflictFiles}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := commitAndSignNoAuthor(ctx, pr, mergeMsg, signArg, tmpBasePath, env); err != nil {
|
|
||||||
return types.MergeResult{}, fmt.Errorf("unable to make final commit: %w", err)
|
|
||||||
}
|
|
||||||
case enum.MergeMethodSquash:
|
|
||||||
// Merge with squash
|
|
||||||
cmd := git.NewCommand(ctx, "merge", "--squash", trackingBranch)
|
|
||||||
result, err := runMergeCommand(ctx, pr, mergeMethod, cmd, tmpBasePath, env)
|
|
||||||
if err != nil {
|
|
||||||
return types.MergeResult{}, fmt.Errorf("unable to merge --squash tracking into base: %w", err)
|
|
||||||
}
|
|
||||||
if len(result.conflictFiles) > 0 {
|
|
||||||
return types.MergeResult{ConflictFiles: result.conflictFiles}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if signArg == "" {
|
|
||||||
if err := git.NewCommand(ctx, "commit", fmt.Sprintf("--author='%s'", identity.String()), "-m", mergeMsg).
|
|
||||||
Run(&git.RunOpts{
|
|
||||||
Env: env,
|
|
||||||
Dir: tmpBasePath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
}); err != nil {
|
|
||||||
return types.MergeResult{}, processGiteaErrorf(err, "git commit [%s -> %s]\n%s\n%s",
|
|
||||||
pr.HeadBranch, pr.BaseBranch, outbuf.String(), errbuf.String())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := git.NewCommand(ctx, "commit", signArg, fmt.Sprintf("--author='%s'", identity.String()), "-m", mergeMsg).
|
|
||||||
Run(&git.RunOpts{
|
|
||||||
Env: env,
|
|
||||||
Dir: tmpBasePath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
}); err != nil {
|
|
||||||
return types.MergeResult{}, processGiteaErrorf(err, "git commit [%s -> %s]\n%s\n%s",
|
|
||||||
pr.HeadBranch, pr.BaseBranch, outbuf.String(), errbuf.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case enum.MergeMethodRebase:
|
|
||||||
// Create staging branch
|
|
||||||
if err := git.NewCommand(ctx, "checkout", "-b", stagingBranch, trackingBranch).
|
|
||||||
Run(&git.RunOpts{
|
|
||||||
Dir: tmpBasePath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
}); err != nil {
|
|
||||||
return types.MergeResult{}, fmt.Errorf(
|
|
||||||
"git checkout base prior to merge post staging rebase [%s -> %s]: %w\n%s\n%s",
|
|
||||||
pr.HeadBranch, pr.BaseBranch, err, outbuf.String(), errbuf.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
outbuf.Reset()
|
|
||||||
errbuf.Reset()
|
|
||||||
|
|
||||||
var conflicts bool
|
|
||||||
|
|
||||||
// Rebase before merging
|
|
||||||
if err := git.NewCommand(ctx, "rebase", baseBranch).
|
|
||||||
Run(&git.RunOpts{
|
|
||||||
Dir: tmpBasePath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
}); err != nil {
|
|
||||||
// Rebase will leave a REBASE_HEAD file in .git if there is a conflict
|
|
||||||
if _, statErr := os.Stat(filepath.Join(tmpBasePath, ".git", "REBASE_HEAD")); statErr == nil {
|
|
||||||
// Rebase works by processing commit by commit. To get the full list of conflict files
|
|
||||||
// all commits would have to be applied. It's simpler to revert the rebase and
|
|
||||||
// get the list conflict using git merge.
|
|
||||||
conflicts = true
|
|
||||||
} else {
|
|
||||||
return types.MergeResult{}, fmt.Errorf(
|
|
||||||
"git rebase staging on to base [%s -> %s]: %w\n%s\n%s",
|
|
||||||
pr.HeadBranch, pr.BaseBranch, err, outbuf.String(), errbuf.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outbuf.Reset()
|
|
||||||
errbuf.Reset()
|
|
||||||
|
|
||||||
if conflicts {
|
|
||||||
// Rebase failed because there are conflicts. Abort the rebase.
|
|
||||||
if err := git.NewCommand(ctx, "rebase", "--abort").
|
|
||||||
Run(&git.RunOpts{
|
|
||||||
Dir: tmpBasePath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
}); err != nil {
|
|
||||||
return types.MergeResult{}, fmt.Errorf(
|
|
||||||
"git abort rebase [%s -> %s]: %w\n%s\n%s",
|
|
||||||
pr.HeadBranch, pr.BaseBranch, err, outbuf.String(), errbuf.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
outbuf.Reset()
|
|
||||||
errbuf.Reset()
|
|
||||||
|
|
||||||
// Go back to the base branch.
|
|
||||||
if err := git.NewCommand(ctx, "checkout", baseBranch).
|
|
||||||
Run(&git.RunOpts{
|
|
||||||
Dir: tmpBasePath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
}); err != nil {
|
|
||||||
return types.MergeResult{}, fmt.Errorf(
|
|
||||||
"return to the base branch [%s -> %s]: %w\n%s\n%s",
|
|
||||||
pr.HeadBranch, pr.BaseBranch, err, outbuf.String(), errbuf.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
outbuf.Reset()
|
|
||||||
errbuf.Reset()
|
|
||||||
|
|
||||||
// Run the ordinary merge to get the list of conflict files.
|
|
||||||
cmd := git.NewCommand(ctx, "merge", "--no-ff", "--no-commit", trackingBranch)
|
|
||||||
result, err := runMergeCommand(ctx, pr, mergeMethod, cmd, tmpBasePath, env)
|
|
||||||
if err != nil {
|
|
||||||
return types.MergeResult{}, fmt.Errorf(
|
|
||||||
"git abort rebase [%s -> %s]: %w\n%s\n%s",
|
|
||||||
pr.HeadBranch, pr.BaseBranch, err, outbuf.String(), errbuf.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if len(result.conflictFiles) > 0 {
|
|
||||||
return types.MergeResult{ConflictFiles: result.conflictFiles}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return types.MergeResult{}, errors.New("rebase reported conflicts, but merge gave no conflict files")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checkout base branch again
|
|
||||||
if err := git.NewCommand(ctx, "checkout", baseBranch).
|
|
||||||
Run(&git.RunOpts{
|
|
||||||
Dir: tmpBasePath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
}); err != nil {
|
|
||||||
return types.MergeResult{}, fmt.Errorf(
|
|
||||||
"git checkout base prior to merge post staging rebase [%s -> %s]: %w\n%s\n%s",
|
|
||||||
pr.HeadBranch, pr.BaseBranch, err, outbuf.String(), errbuf.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
outbuf.Reset()
|
|
||||||
errbuf.Reset()
|
|
||||||
|
|
||||||
// Prepare merge with commit
|
|
||||||
cmd := git.NewCommand(ctx, "merge", "--ff-only", stagingBranch)
|
|
||||||
result, err := runMergeCommand(ctx, pr, mergeMethod, cmd, tmpBasePath, env)
|
|
||||||
if err != nil {
|
|
||||||
return types.MergeResult{}, fmt.Errorf("unable to ff-olny merge tracking into base: %w", err)
|
|
||||||
}
|
|
||||||
if len(result.conflictFiles) > 0 {
|
|
||||||
return types.MergeResult{ConflictFiles: result.conflictFiles}, nil
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return types.MergeResult{}, fmt.Errorf("wrong merge method provided: %s", mergeMethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
return types.MergeResult{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func conflictFiles(
|
|
||||||
ctx context.Context,
|
|
||||||
pr *types.PullRequest,
|
|
||||||
env []string,
|
|
||||||
repoPath string,
|
|
||||||
) (files []string, err error) {
|
|
||||||
stdout, stderr, err := git.NewCommand(
|
|
||||||
ctx, "diff", "--name-only", "--diff-filter=U", "--relative",
|
|
||||||
).RunStdString(&git.RunOpts{
|
|
||||||
Env: env,
|
|
||||||
Dir: repoPath,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, processGiteaErrorf(err, "failed to list conflict files [%s -> %s], stderr: %v, err: %v",
|
|
||||||
pr.HeadBranch, pr.BaseBranch, stderr, err)
|
|
||||||
}
|
|
||||||
if len(stdout) > 0 {
|
|
||||||
files = strings.Split(stdout[:len(stdout)-1], "\n")
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Adapter) GetDiffTree(
|
|
||||||
ctx context.Context,
|
|
||||||
repoPath string,
|
|
||||||
baseBranch string,
|
|
||||||
headBranch string,
|
|
||||||
) (string, error) {
|
|
||||||
if repoPath == "" {
|
|
||||||
return "", ErrRepositoryPathEmpty
|
|
||||||
}
|
|
||||||
getDiffTreeFromBranch := func(repoPath, baseBranch, headBranch string) (string, error) {
|
|
||||||
var outbuf, errbuf strings.Builder
|
|
||||||
if err := git.NewCommand(ctx, "diff-tree", "--no-commit-id",
|
|
||||||
"--name-only", "-r", "-z", "--root", baseBranch, headBranch, "--").
|
|
||||||
Run(&git.RunOpts{
|
|
||||||
Dir: repoPath,
|
|
||||||
Stdout: &outbuf,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
}); err != nil {
|
|
||||||
giteaErr := &giteaRunStdError{err: err, stderr: errbuf.String()}
|
|
||||||
return "", processGiteaErrorf(giteaErr, "git diff-tree [%s base:%s head:%s]: %s",
|
|
||||||
repoPath, baseBranch, headBranch, errbuf.String())
|
|
||||||
}
|
|
||||||
return outbuf.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
scanNullTerminatedStrings := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
|
||||||
if atEOF && len(data) == 0 {
|
|
||||||
return 0, nil, nil
|
|
||||||
}
|
|
||||||
if i := bytes.IndexByte(data, '\x00'); i >= 0 {
|
|
||||||
return i + 1, data[0:i], nil
|
|
||||||
}
|
|
||||||
if atEOF {
|
|
||||||
return len(data), data, nil
|
|
||||||
}
|
|
||||||
return 0, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
list, err := getDiffTreeFromBranch(repoPath, baseBranch, headBranch)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefixing '/' for each entry, otherwise all files with the same name in subdirectories would be matched.
|
|
||||||
out := bytes.Buffer{}
|
|
||||||
scanner := bufio.NewScanner(strings.NewReader(list))
|
|
||||||
scanner.Split(scanNullTerminatedStrings)
|
|
||||||
for scanner.Scan() {
|
|
||||||
filepath := scanner.Text()
|
|
||||||
// escape '*', '?', '[', spaces and '!' prefix
|
|
||||||
filepath = escapedSymbols.ReplaceAllString(filepath, `\$1`)
|
|
||||||
// no necessary to escape the first '#' symbol because the first symbol is '/'
|
|
||||||
fmt.Fprintf(&out, "/%s\n", filepath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return out.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMergeBase checks and returns merge base of two branches and the reference used as base.
|
|
||||||
func (a Adapter) GetMergeBase(
|
|
||||||
ctx context.Context,
|
|
||||||
repoPath string,
|
|
||||||
remote string,
|
|
||||||
base string,
|
|
||||||
head string,
|
|
||||||
) (string, string, error) {
|
|
||||||
if repoPath == "" {
|
|
||||||
return "", "", ErrRepositoryPathEmpty
|
|
||||||
}
|
|
||||||
if remote == "" {
|
|
||||||
remote = "origin"
|
|
||||||
}
|
|
||||||
|
|
||||||
if remote != "origin" {
|
|
||||||
tmpBaseName := git.RemotePrefix + remote + "/tmp_" + base
|
|
||||||
// Fetch commit into a temporary branch in order to be able to handle commits and tags
|
|
||||||
_, _, err := git.NewCommand(ctx, "fetch", "--no-tags", remote, "--",
|
|
||||||
base+":"+tmpBaseName).RunStdString(&git.RunOpts{Dir: repoPath})
|
|
||||||
if err == nil {
|
|
||||||
base = tmpBaseName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout, stderr, err := git.NewCommand(ctx, "merge-base", "--", base, head).RunStdString(&git.RunOpts{Dir: repoPath})
|
|
||||||
if err != nil {
|
|
||||||
return "", "", processGiteaErrorf(err, "failed to get merge-base: %v", stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.TrimSpace(stdout), base, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAncestor returns if the provided commit SHA is ancestor of the other commit SHA.
|
|
||||||
func (a Adapter) IsAncestor(
|
|
||||||
ctx context.Context,
|
|
||||||
repoPath string,
|
|
||||||
ancestorCommitSHA, descendantCommitSHA string,
|
|
||||||
) (bool, error) {
|
|
||||||
if repoPath == "" {
|
|
||||||
return false, ErrRepositoryPathEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
_, stderr, runErr := git.NewCommand(ctx, "merge-base", "--is-ancestor", ancestorCommitSHA, descendantCommitSHA).
|
|
||||||
RunStdString(&git.RunOpts{Dir: repoPath})
|
|
||||||
if runErr != nil {
|
|
||||||
if runErr.IsExitCode(1) && stderr == "" {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, processGiteaErrorf(runErr, "failed to check commit ancestry: %v", stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// giteaRunStdError is an implementation of the RunStdError interface in the gitea codebase.
|
|
||||||
// It allows us to process gitea errors even when using cmd.Run() instead of cmd.RunStdString() or run.StdBytes().
|
|
||||||
type giteaRunStdError struct {
|
|
||||||
err error
|
|
||||||
stderr string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *giteaRunStdError) Error() string {
|
|
||||||
return fmt.Sprintf("failed with %s, error output: %s", e.err, e.stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *giteaRunStdError) Unwrap() error {
|
|
||||||
return e.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *giteaRunStdError) Stderr() string {
|
|
||||||
return e.stderr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *giteaRunStdError) IsExitCode(code int) bool {
|
|
||||||
var exitError *exec.ExitError
|
|
||||||
if errors.As(e.err, &exitError) {
|
|
||||||
return exitError.ExitCode() == code
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
@ -1,123 +0,0 @@
|
|||||||
// Copyright 2023 Harness, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package adapter_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/harness/gitness/git/adapter"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAdapter_GetMergeBase(t *testing.T) {
|
|
||||||
git := setupGit(t)
|
|
||||||
repo, teardown := setupRepo(t, git, "testmergebase")
|
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
baseBranch := "main"
|
|
||||||
// write file to main branch
|
|
||||||
_, parentSHA := writeFile(t, repo, "file1.txt", "some content", nil)
|
|
||||||
|
|
||||||
err := repo.SetReference("refs/heads/"+baseBranch, parentSHA.String())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed updating reference '%s': %v", baseBranch, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
baseTag := "0.0.1"
|
|
||||||
err = repo.CreateAnnotatedTag(baseTag, "test tag 1", parentSHA.String())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error creating annotated tag '%s': %v", baseTag, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
headBranch := "dev"
|
|
||||||
|
|
||||||
// create branch
|
|
||||||
err = repo.CreateBranch(headBranch, baseBranch)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed creating a branch '%s': %v", headBranch, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// write file to main branch
|
|
||||||
_, sha := writeFile(t, repo, "file1.txt", "new content", []string{parentSHA.String()})
|
|
||||||
|
|
||||||
err = repo.SetReference("refs/heads/"+headBranch, sha.String())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed updating reference '%s': %v", headBranch, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
headTag := "0.0.2"
|
|
||||||
err = repo.CreateAnnotatedTag(headTag, "test tag 2", sha.String())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error creating annotated tag '%s': %v", headTag, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
repoPath string
|
|
||||||
remote string
|
|
||||||
base string
|
|
||||||
head string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
git adapter.Adapter
|
|
||||||
args args
|
|
||||||
want string
|
|
||||||
want1 string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "git merge base using branch names",
|
|
||||||
git: git,
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
repoPath: repo.Path,
|
|
||||||
remote: "",
|
|
||||||
base: baseBranch,
|
|
||||||
head: headBranch,
|
|
||||||
},
|
|
||||||
want: parentSHA.String(),
|
|
||||||
want1: baseBranch,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "git merge base using annotated tags",
|
|
||||||
git: git,
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
repoPath: repo.Path,
|
|
||||||
remote: "",
|
|
||||||
base: baseTag,
|
|
||||||
head: headTag,
|
|
||||||
},
|
|
||||||
want: parentSHA.String(),
|
|
||||||
want1: baseTag,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, got1, err := tt.git.GetMergeBase(tt.args.ctx, tt.args.repoPath, tt.args.remote, tt.args.base, tt.args.head)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("GetMergeBase() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("GetMergeBase() got = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
if got1 != tt.want1 {
|
|
||||||
t.Errorf("GetMergeBase() got1 = %v, want %v", got1, tt.want1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
// Copyright 2023 Harness, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package adapter
|
|
||||||
|
|
||||||
// ObjectType git object type.
|
|
||||||
type ObjectType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ObjectCommit commit object type.
|
|
||||||
ObjectCommit ObjectType = "commit"
|
|
||||||
// ObjectTree tree object type.
|
|
||||||
ObjectTree ObjectType = "tree"
|
|
||||||
// ObjectBlob blob object type.
|
|
||||||
ObjectBlob ObjectType = "blob"
|
|
||||||
// ObjectTag tag object type.
|
|
||||||
ObjectTag ObjectType = "tag"
|
|
||||||
// ObjectBranch branch object type.
|
|
||||||
ObjectBranch ObjectType = "branch"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Bytes returns the byte array for the Object Type.
|
|
||||||
func (o ObjectType) Bytes() []byte {
|
|
||||||
return []byte(o)
|
|
||||||
}
|
|
@ -1,136 +0,0 @@
|
|||||||
// Copyright 2023 Harness, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package adapter_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/harness/gitness/git/adapter"
|
|
||||||
"github.com/harness/gitness/git/hook"
|
|
||||||
"github.com/harness/gitness/git/types"
|
|
||||||
|
|
||||||
gitea "code.gitea.io/gitea/modules/git"
|
|
||||||
)
|
|
||||||
|
|
||||||
type teardown func()
|
|
||||||
|
|
||||||
var (
|
|
||||||
testAuthor = &gitea.Signature{
|
|
||||||
Name: "test",
|
|
||||||
Email: "test@test.com",
|
|
||||||
}
|
|
||||||
|
|
||||||
testCommitter = &gitea.Signature{
|
|
||||||
Name: "test",
|
|
||||||
Email: "test@test.com",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type mockClientFactory struct{}
|
|
||||||
|
|
||||||
func (f *mockClientFactory) NewClient(_ context.Context, _ map[string]string) (hook.Client, error) {
|
|
||||||
return hook.NewNoopClient([]string{"mocked client"}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupGit(t *testing.T) adapter.Adapter {
|
|
||||||
t.Helper()
|
|
||||||
git, err := adapter.New(
|
|
||||||
types.Config{Trace: true},
|
|
||||||
adapter.NewInMemoryLastCommitCache(5*time.Minute),
|
|
||||||
&mockClientFactory{},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error initializing repository: %v", err)
|
|
||||||
}
|
|
||||||
return git
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupRepo(t *testing.T, git adapter.Adapter, name string) (*gitea.Repository, teardown) {
|
|
||||||
t.Helper()
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
tmpdir := os.TempDir()
|
|
||||||
|
|
||||||
repoPath := path.Join(tmpdir, "test_repos", name)
|
|
||||||
|
|
||||||
err := git.InitRepository(ctx, repoPath, true)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error initializing repository: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
repo, err := git.OpenRepository(ctx, repoPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error opening repository '%s': %v", name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = repo.SetDefaultBranch("main")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error setting default branch 'main': %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = git.Config(ctx, repoPath, "user.email", testCommitter.Email)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error setting config user.email %s: %v", testCommitter.Email, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = git.Config(ctx, repoPath, "user.name", testCommitter.Name)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error setting config user.name %s: %v", testCommitter.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo, func() {
|
|
||||||
if err := os.RemoveAll(repoPath); err != nil {
|
|
||||||
t.Errorf("error while removeng the repository '%s'", repoPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeFile(
|
|
||||||
t *testing.T,
|
|
||||||
repo *gitea.Repository,
|
|
||||||
path string,
|
|
||||||
content string,
|
|
||||||
parents []string,
|
|
||||||
) (oid gitea.SHA1, commitSha gitea.SHA1) {
|
|
||||||
t.Helper()
|
|
||||||
oid, err := repo.HashObject(strings.NewReader(content))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to hash object: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = repo.AddObjectToIndex("100644", oid, path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to add object to index: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tree, err := repo.WriteTree()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to write tree: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
commitSha, err = repo.CommitTree(testAuthor, testCommitter, tree, gitea.CommitTreeOpts{
|
|
||||||
Message: "write file operation",
|
|
||||||
Parents: parents,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to commit tree: %v", err)
|
|
||||||
}
|
|
||||||
return oid, commitSha
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
// Copyright 2023 Harness, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/harness/gitness/git/types"
|
|
||||||
|
|
||||||
gitea "code.gitea.io/gitea/modules/git"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetSubmodule returns the submodule at the given path reachable from ref.
|
|
||||||
// Note: ref can be Branch / Tag / CommitSHA.
|
|
||||||
func (a Adapter) GetSubmodule(
|
|
||||||
ctx context.Context,
|
|
||||||
repoPath string,
|
|
||||||
ref string,
|
|
||||||
treePath string,
|
|
||||||
) (*types.Submodule, error) {
|
|
||||||
if repoPath == "" {
|
|
||||||
return nil, ErrRepositoryPathEmpty
|
|
||||||
}
|
|
||||||
treePath = cleanTreePath(treePath)
|
|
||||||
|
|
||||||
giteaRepo, err := gitea.OpenRepository(ctx, repoPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, processGiteaErrorf(err, "failed to open repository")
|
|
||||||
}
|
|
||||||
defer giteaRepo.Close()
|
|
||||||
|
|
||||||
// Get the giteaCommit object for the ref
|
|
||||||
giteaCommit, err := giteaRepo.GetCommit(ref)
|
|
||||||
if err != nil {
|
|
||||||
return nil, processGiteaErrorf(err, "error getting commit for ref '%s'", ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
giteaSubmodule, err := giteaCommit.GetSubModule(treePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, processGiteaErrorf(err, "error getting submodule '%s' from commit", treePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &types.Submodule{
|
|
||||||
Name: giteaSubmodule.Name,
|
|
||||||
URL: giteaSubmodule.URL,
|
|
||||||
}, nil
|
|
||||||
}
|
|
@ -12,39 +12,26 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/harness/gitness/cache"
|
"github.com/harness/gitness/cache"
|
||||||
"github.com/harness/gitness/git/hook"
|
"github.com/harness/gitness/git/hook"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/types"
|
||||||
|
|
||||||
gitea "code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Adapter struct {
|
type Git struct {
|
||||||
traceGit bool
|
traceGit bool
|
||||||
lastCommitCache cache.Cache[CommitEntryKey, *types.Commit]
|
lastCommitCache cache.Cache[CommitEntryKey, *Commit]
|
||||||
githookFactory hook.ClientFactory
|
githookFactory hook.ClientFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
config types.Config,
|
config types.Config,
|
||||||
lastCommitCache cache.Cache[CommitEntryKey, *types.Commit],
|
lastCommitCache cache.Cache[CommitEntryKey, *Commit],
|
||||||
githookFactory hook.ClientFactory,
|
githookFactory hook.ClientFactory,
|
||||||
) (Adapter, error) {
|
) (*Git, error) {
|
||||||
// TODO: should be subdir of gitRoot? What is it being used for?
|
return &Git{
|
||||||
setting.Git.HomePath = "home"
|
|
||||||
|
|
||||||
err := gitea.InitSimple(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
return Adapter{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return Adapter{
|
|
||||||
traceGit: config.Trace,
|
traceGit: config.Trace,
|
||||||
lastCommitCache: lastCommitCache,
|
lastCommitCache: lastCommitCache,
|
||||||
githookFactory: githookFactory,
|
githookFactory: githookFactory,
|
@ -12,14 +12,14 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/sha"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -70,19 +70,19 @@ func testParseSignatureFromCatFileLineFor(t *testing.T, name string, email strin
|
|||||||
|
|
||||||
func TestParseTagDataFromCatFile(t *testing.T) {
|
func TestParseTagDataFromCatFile(t *testing.T) {
|
||||||
when, _ := time.Parse(defaultGitTimeLayout, "Fri Sep 23 10:57:49 2022 -0700")
|
when, _ := time.Parse(defaultGitTimeLayout, "Fri Sep 23 10:57:49 2022 -0700")
|
||||||
testParseTagDataFromCatFileFor(t, "sha012", types.GitObjectTypeTag, "name1",
|
testParseTagDataFromCatFileFor(t, sha.EmptyTree, GitObjectTypeTag, "name1",
|
||||||
types.Signature{Identity: types.Identity{Name: "max", Email: "max@mail.com"}, When: when},
|
Signature{Identity: Identity{Name: "max", Email: "max@mail.com"}, When: when},
|
||||||
"some message", "some message")
|
"some message", "some message")
|
||||||
|
|
||||||
// test with signature
|
// test with signature
|
||||||
testParseTagDataFromCatFileFor(t, "sha012", types.GitObjectTypeCommit, "name2",
|
testParseTagDataFromCatFileFor(t, sha.EmptyTree, GitObjectTypeCommit, "name2",
|
||||||
types.Signature{Identity: types.Identity{Name: "max", Email: "max@mail.com"}, When: when},
|
Signature{Identity: Identity{Name: "max", Email: "max@mail.com"}, When: when},
|
||||||
"gpgsig -----BEGIN PGP SIGNATURE-----\n\nw...B\n-----END PGP SIGNATURE-----\n\nsome message",
|
"gpgsig -----BEGIN PGP SIGNATURE-----\n\nw...B\n-----END PGP SIGNATURE-----\n\nsome message",
|
||||||
"some message")
|
"some message")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testParseTagDataFromCatFileFor(t *testing.T, object string, typ types.GitObjectType, name string,
|
func testParseTagDataFromCatFileFor(t *testing.T, object string, typ GitObjectType, name string,
|
||||||
tagger types.Signature, remainder string, expectedMessage string) {
|
tagger Signature, remainder string, expectedMessage string) {
|
||||||
data := fmt.Sprintf(
|
data := fmt.Sprintf(
|
||||||
"object %s\ntype %s\ntag %s\ntagger %s <%s> %s\n%s",
|
"object %s\ntype %s\ntag %s\ntagger %s <%s> %s\n%s",
|
||||||
object, string(typ), name,
|
object, string(typ), name,
|
||||||
@ -92,7 +92,7 @@ func testParseTagDataFromCatFileFor(t *testing.T, object string, typ types.GitOb
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, name, res.Name, data)
|
require.Equal(t, name, res.Name, data)
|
||||||
require.Equal(t, object, res.TargetSha, data)
|
require.Equal(t, object, res.TargetSha.String(), data)
|
||||||
require.Equal(t, typ, res.TargetType, data)
|
require.Equal(t, typ, res.TargetType, data)
|
||||||
require.Equal(t, expectedMessage, res.Message, data)
|
require.Equal(t, expectedMessage, res.Message, data)
|
||||||
require.Equal(t, tagger.Identity.Name, res.Tagger.Identity.Name, data)
|
require.Equal(t, tagger.Identity.Name, res.Tagger.Identity.Name, data)
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@ -26,7 +26,7 @@ import (
|
|||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git/command"
|
"github.com/harness/gitness/git/command"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/sha"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -36,14 +36,23 @@ var (
|
|||||||
blamePorcelainOutOfRangeErrorRE = regexp.MustCompile(`has only \d+ lines$`)
|
blamePorcelainOutOfRangeErrorRE = regexp.MustCompile(`has only \d+ lines$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a Adapter) Blame(
|
type BlamePart struct {
|
||||||
|
Commit *Commit `json:"commit"`
|
||||||
|
Lines []string `json:"lines"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlameNextReader interface {
|
||||||
|
NextPart() (*BlamePart, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Git) Blame(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
rev string,
|
rev string,
|
||||||
file string,
|
file string,
|
||||||
lineFrom int,
|
lineFrom int,
|
||||||
lineTo int,
|
lineTo int,
|
||||||
) types.BlameReader {
|
) BlameNextReader {
|
||||||
// prepare the git command line arguments
|
// prepare the git command line arguments
|
||||||
cmd := command.New(
|
cmd := command.New(
|
||||||
"blame",
|
"blame",
|
||||||
@ -84,7 +93,7 @@ func (a Adapter) Blame(
|
|||||||
|
|
||||||
return &BlameReader{
|
return &BlameReader{
|
||||||
scanner: bufio.NewScanner(pipeRead),
|
scanner: bufio.NewScanner(pipeRead),
|
||||||
commitCache: make(map[string]*types.Commit),
|
commitCache: make(map[string]*Commit),
|
||||||
errReader: stderr, // Any stderr output will cause the BlameReader to fail.
|
errReader: stderr, // Any stderr output will cause the BlameReader to fail.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,7 +101,7 @@ func (a Adapter) Blame(
|
|||||||
type BlameReader struct {
|
type BlameReader struct {
|
||||||
scanner *bufio.Scanner
|
scanner *bufio.Scanner
|
||||||
lastLine string
|
lastLine string
|
||||||
commitCache map[string]*types.Commit
|
commitCache map[string]*Commit
|
||||||
errReader io.Reader
|
errReader io.Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,8 +130,8 @@ func (r *BlameReader) unreadLine(line string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//nolint:complexity,gocognit,nestif // it's ok
|
//nolint:complexity,gocognit,nestif // it's ok
|
||||||
func (r *BlameReader) NextPart() (*types.BlamePart, error) {
|
func (r *BlameReader) NextPart() (*BlamePart, error) {
|
||||||
var commit *types.Commit
|
var commit *Commit
|
||||||
var lines []string
|
var lines []string
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@ -134,12 +143,12 @@ func (r *BlameReader) NextPart() (*types.BlamePart, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if matches := blamePorcelainHeadRE.FindStringSubmatch(line); matches != nil {
|
if matches := blamePorcelainHeadRE.FindStringSubmatch(line); matches != nil {
|
||||||
sha := matches[1]
|
commitSHA := sha.Must(matches[1])
|
||||||
|
|
||||||
if commit == nil {
|
if commit == nil {
|
||||||
commit = r.commitCache[sha]
|
commit = r.commitCache[commitSHA.String()]
|
||||||
if commit == nil {
|
if commit == nil {
|
||||||
commit = &types.Commit{SHA: sha}
|
commit = &Commit{SHA: commitSHA}
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches[5] != "" {
|
if matches[5] != "" {
|
||||||
@ -153,11 +162,11 @@ func (r *BlameReader) NextPart() (*types.BlamePart, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if sha != commit.SHA {
|
if !commit.SHA.Equal(commitSHA) {
|
||||||
r.unreadLine(line)
|
r.unreadLine(line)
|
||||||
r.commitCache[commit.SHA] = commit
|
r.commitCache[commit.SHA.String()] = commit
|
||||||
|
|
||||||
return &types.BlamePart{
|
return &BlamePart{
|
||||||
Commit: commit,
|
Commit: commit,
|
||||||
Lines: lines,
|
Lines: lines,
|
||||||
}, nil
|
}, nil
|
||||||
@ -210,10 +219,10 @@ func (r *BlameReader) NextPart() (*types.BlamePart, error) {
|
|||||||
return nil, errors.Internal(err, "failed to start git blame command")
|
return nil, errors.Internal(err, "failed to start git blame command")
|
||||||
}
|
}
|
||||||
|
|
||||||
var part *types.BlamePart
|
var part *BlamePart
|
||||||
|
|
||||||
if commit != nil && len(lines) > 0 {
|
if commit != nil && len(lines) > 0 {
|
||||||
part = &types.BlamePart{
|
part = &BlamePart{
|
||||||
Commit: commit,
|
Commit: commit,
|
||||||
Lines: lines,
|
Lines: lines,
|
||||||
}
|
}
|
||||||
@ -222,7 +231,7 @@ func (r *BlameReader) NextPart() (*types.BlamePart, error) {
|
|||||||
return part, err
|
return part, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBlameHeaders(line string, commit *types.Commit) {
|
func parseBlameHeaders(line string, commit *Commit) {
|
||||||
// This is the list of git blame headers that we process. Other headers we ignore.
|
// This is the list of git blame headers that we process. Other headers we ignore.
|
||||||
const (
|
const (
|
||||||
headerSummary = "summary "
|
headerSummary = "summary "
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@ -23,7 +23,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/sha"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
)
|
)
|
||||||
@ -64,44 +64,44 @@ filename file_name.go
|
|||||||
Line 14
|
Line 14
|
||||||
`
|
`
|
||||||
|
|
||||||
author := types.Identity{
|
author := Identity{
|
||||||
Name: "Marko",
|
Name: "Marko",
|
||||||
Email: "marko.gacesa@harness.io",
|
Email: "marko.gacesa@harness.io",
|
||||||
}
|
}
|
||||||
committer := types.Identity{
|
committer := Identity{
|
||||||
Name: "Committer",
|
Name: "Committer",
|
||||||
Email: "noreply@harness.io",
|
Email: "noreply@harness.io",
|
||||||
}
|
}
|
||||||
|
|
||||||
commit1 := &types.Commit{
|
commit1 := &Commit{
|
||||||
SHA: "16f267ad4f731af1b2e36f42e170ed8921377398",
|
SHA: sha.Must("16f267ad4f731af1b2e36f42e170ed8921377398"),
|
||||||
Title: "Pull request 1",
|
Title: "Pull request 1",
|
||||||
Message: "",
|
Message: "",
|
||||||
Author: types.Signature{
|
Author: Signature{
|
||||||
Identity: author,
|
Identity: author,
|
||||||
When: time.Unix(1669812989, 0),
|
When: time.Unix(1669812989, 0),
|
||||||
},
|
},
|
||||||
Committer: types.Signature{
|
Committer: Signature{
|
||||||
Identity: committer,
|
Identity: committer,
|
||||||
When: time.Unix(1669812989, 0),
|
When: time.Unix(1669812989, 0),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
commit2 := &types.Commit{
|
commit2 := &Commit{
|
||||||
SHA: "dcb4b6b63e86f06ed4e4c52fbc825545dc0b6200",
|
SHA: sha.Must("dcb4b6b63e86f06ed4e4c52fbc825545dc0b6200"),
|
||||||
Title: "Pull request 2",
|
Title: "Pull request 2",
|
||||||
Message: "",
|
Message: "",
|
||||||
Author: types.Signature{
|
Author: Signature{
|
||||||
Identity: author,
|
Identity: author,
|
||||||
When: time.Unix(1673952128, 0),
|
When: time.Unix(1673952128, 0),
|
||||||
},
|
},
|
||||||
Committer: types.Signature{
|
Committer: Signature{
|
||||||
Identity: committer,
|
Identity: committer,
|
||||||
When: time.Unix(1673952128, 0),
|
When: time.Unix(1673952128, 0),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
want := []*types.BlamePart{
|
want := []*BlamePart{
|
||||||
{
|
{
|
||||||
Commit: commit1,
|
Commit: commit1,
|
||||||
Lines: []string{"Line 10", "Line 11"},
|
Lines: []string{"Line 10", "Line 11"},
|
||||||
@ -118,11 +118,11 @@ filename file_name.go
|
|||||||
|
|
||||||
reader := BlameReader{
|
reader := BlameReader{
|
||||||
scanner: bufio.NewScanner(strings.NewReader(blameOut)),
|
scanner: bufio.NewScanner(strings.NewReader(blameOut)),
|
||||||
commitCache: make(map[string]*types.Commit),
|
commitCache: make(map[string]*Commit),
|
||||||
errReader: strings.NewReader(""),
|
errReader: strings.NewReader(""),
|
||||||
}
|
}
|
||||||
|
|
||||||
var got []*types.BlamePart
|
var got []*BlamePart
|
||||||
|
|
||||||
for {
|
for {
|
||||||
part, err := reader.NextPart()
|
part, err := reader.NextPart()
|
||||||
@ -145,7 +145,7 @@ filename file_name.go
|
|||||||
func TestBlameReader_NextPart_UserError(t *testing.T) {
|
func TestBlameReader_NextPart_UserError(t *testing.T) {
|
||||||
reader := BlameReader{
|
reader := BlameReader{
|
||||||
scanner: bufio.NewScanner(strings.NewReader("")),
|
scanner: bufio.NewScanner(strings.NewReader("")),
|
||||||
commitCache: make(map[string]*types.Commit),
|
commitCache: make(map[string]*Commit),
|
||||||
errReader: strings.NewReader("fatal: no such path\n"),
|
errReader: strings.NewReader("fatal: no such path\n"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ func TestBlameReader_NextPart_UserError(t *testing.T) {
|
|||||||
func TestBlameReader_NextPart_CmdError(t *testing.T) {
|
func TestBlameReader_NextPart_CmdError(t *testing.T) {
|
||||||
reader := BlameReader{
|
reader := BlameReader{
|
||||||
scanner: bufio.NewScanner(iotest.ErrReader(errors.New("dummy error"))),
|
scanner: bufio.NewScanner(iotest.ErrReader(errors.New("dummy error"))),
|
||||||
commitCache: make(map[string]*types.Commit),
|
commitCache: make(map[string]*Commit),
|
||||||
errReader: strings.NewReader(""),
|
errReader: strings.NewReader(""),
|
||||||
}
|
}
|
||||||
|
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -20,48 +20,58 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/sha"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type BlobReader struct {
|
||||||
|
SHA sha.SHA
|
||||||
|
// Size is the actual size of the blob.
|
||||||
|
Size int64
|
||||||
|
// ContentSize is the total number of bytes returned by the Content Reader.
|
||||||
|
ContentSize int64
|
||||||
|
// Content contains the (partial) content of the blob.
|
||||||
|
Content io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
// GetBlob returns the blob for the given object sha.
|
// GetBlob returns the blob for the given object sha.
|
||||||
func (a Adapter) GetBlob(
|
func (g *Git) GetBlob(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
sha string,
|
sha sha.SHA,
|
||||||
sizeLimit int64,
|
sizeLimit int64,
|
||||||
) (*types.BlobReader, error) {
|
) (*BlobReader, error) {
|
||||||
stdIn, stdOut, cancel := CatFileBatch(ctx, repoPath)
|
stdIn, stdOut, cancel := CatFileBatch(ctx, repoPath)
|
||||||
|
line := sha.String() + "\n"
|
||||||
_, err := stdIn.Write([]byte(sha + "\n"))
|
_, err := stdIn.Write([]byte(line))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
return nil, fmt.Errorf("failed to write blob sha to git stdin: %w", err)
|
return nil, fmt.Errorf("failed to write blob sha to git stdin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objectSHA, objectType, objectSize, err := ReadBatchHeaderLine(stdOut)
|
output, err := ReadBatchHeaderLine(stdOut)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
return nil, processGiteaErrorf(err, "failed to read cat-file batch line")
|
return nil, processGitErrorf(err, "failed to read cat-file batch line")
|
||||||
}
|
}
|
||||||
|
|
||||||
if string(objectSHA) != sha {
|
if !output.SHA.Equal(sha) {
|
||||||
cancel()
|
cancel()
|
||||||
return nil, fmt.Errorf("cat-file returned object sha '%s' but expected '%s'", objectSHA, sha)
|
return nil, fmt.Errorf("cat-file returned object sha '%s' but expected '%s'", output.SHA, sha)
|
||||||
}
|
}
|
||||||
if objectType != string(ObjectBlob) {
|
if output.Type != string(GitObjectTypeBlob) {
|
||||||
cancel()
|
cancel()
|
||||||
return nil, errors.InvalidArgument(
|
return nil, errors.InvalidArgument(
|
||||||
"cat-file returned object type '%s' but expected '%s'", objectType, ObjectBlob)
|
"cat-file returned object type '%s' but expected '%s'", output.Type, GitObjectTypeBlob)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentSize := objectSize
|
contentSize := output.Size
|
||||||
if sizeLimit > 0 && sizeLimit < contentSize {
|
if sizeLimit > 0 && sizeLimit < contentSize {
|
||||||
contentSize = sizeLimit
|
contentSize = sizeLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
return &types.BlobReader{
|
return &BlobReader{
|
||||||
SHA: sha,
|
SHA: sha,
|
||||||
Size: objectSize,
|
Size: output.Size,
|
||||||
ContentSize: contentSize,
|
ContentSize: contentSize,
|
||||||
Content: newLimitReaderCloser(stdOut, contentSize, cancel),
|
Content: newLimitReaderCloser(stdOut, contentSize, cancel),
|
||||||
}, nil
|
}, nil
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -21,15 +21,33 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/command"
|
"github.com/harness/gitness/git/command"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/sha"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Branch struct {
|
||||||
|
Name string
|
||||||
|
SHA sha.SHA
|
||||||
|
Commit *Commit
|
||||||
|
}
|
||||||
|
|
||||||
|
type BranchFilter struct {
|
||||||
|
Query string
|
||||||
|
Page int32
|
||||||
|
PageSize int32
|
||||||
|
Sort GitReferenceField
|
||||||
|
Order SortOrder
|
||||||
|
IncludeCommit bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// BranchPrefix base dir of the branch information file store on git.
|
||||||
|
const BranchPrefix = "refs/heads/"
|
||||||
|
|
||||||
// GetBranch gets an existing branch.
|
// GetBranch gets an existing branch.
|
||||||
func (a Adapter) GetBranch(
|
func (g *Git) GetBranch(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
branchName string,
|
branchName string,
|
||||||
) (*types.Branch, error) {
|
) (*Branch, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return nil, ErrRepositoryPathEmpty
|
return nil, ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
@ -38,12 +56,12 @@ func (a Adapter) GetBranch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ref := GetReferenceFromBranchName(branchName)
|
ref := GetReferenceFromBranchName(branchName)
|
||||||
commit, err := GetCommit(ctx, repoPath, ref, "")
|
commit, err := GetCommit(ctx, repoPath, ref+"^{commit}")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to find the commit for the branch: %w", err)
|
return nil, fmt.Errorf("failed to find the commit for the branch: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &types.Branch{
|
return &Branch{
|
||||||
Name: branchName,
|
Name: branchName,
|
||||||
SHA: commit.SHA,
|
SHA: commit.SHA,
|
||||||
Commit: commit,
|
Commit: commit,
|
||||||
@ -53,7 +71,7 @@ func (a Adapter) GetBranch(
|
|||||||
// HasBranches returns true iff there's at least one branch in the repo (any branch)
|
// HasBranches returns true iff there's at least one branch in the repo (any branch)
|
||||||
// NOTE: This is different from repo.Empty(),
|
// NOTE: This is different from repo.Empty(),
|
||||||
// as it doesn't care whether the existing branch is the default branch or not.
|
// as it doesn't care whether the existing branch is the default branch or not.
|
||||||
func (a Adapter) HasBranches(
|
func (g *Git) HasBranches(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
) (bool, error) {
|
) (bool, error) {
|
||||||
@ -68,8 +86,21 @@ func (a Adapter) HasBranches(
|
|||||||
)
|
)
|
||||||
output := &bytes.Buffer{}
|
output := &bytes.Buffer{}
|
||||||
if err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(output)); err != nil {
|
if err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(output)); err != nil {
|
||||||
return false, processGiteaErrorf(err, "failed to trigger rev-list command")
|
return false, processGitErrorf(err, "failed to trigger rev-list command")
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.TrimSpace(output.String()) == "", nil
|
return strings.TrimSpace(output.String()) == "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Git) IsBranchExist(ctx context.Context, repoPath, name string) (bool, error) {
|
||||||
|
cmd := command.New("show-ref",
|
||||||
|
command.WithFlag("--verify", BranchPrefix+name),
|
||||||
|
)
|
||||||
|
err := cmd.Run(ctx,
|
||||||
|
command.WithDir(repoPath),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to check if branch '%s' exist: %w", name, err)
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@ -24,10 +24,10 @@ import (
|
|||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git/command"
|
"github.com/harness/gitness/git/command"
|
||||||
|
"github.com/harness/gitness/git/sha"
|
||||||
|
|
||||||
"github.com/djherbis/buffer"
|
"github.com/djherbis/buffer"
|
||||||
"github.com/djherbis/nio/v3"
|
"github.com/djherbis/nio/v3"
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// WriteCloserError wraps an io.WriteCloser with an additional CloseWithError function.
|
// WriteCloserError wraps an io.WriteCloser with an additional CloseWithError function.
|
||||||
@ -41,6 +41,7 @@ type WriteCloserError interface {
|
|||||||
func CatFileBatch(
|
func CatFileBatch(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
|
flags ...command.CmdOptionFunc,
|
||||||
) (WriteCloserError, *bufio.Reader, func()) {
|
) (WriteCloserError, *bufio.Reader, func()) {
|
||||||
const bufferSize = 32 * 1024
|
const bufferSize = 32 * 1024
|
||||||
// We often want to feed the commits in order into cat-file --batch,
|
// We often want to feed the commits in order into cat-file --batch,
|
||||||
@ -64,7 +65,10 @@ func CatFileBatch(
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
stderr := bytes.Buffer{}
|
stderr := bytes.Buffer{}
|
||||||
cmd := command.New("cat-file", command.WithFlag("--batch"))
|
cmd := command.New("cat-file",
|
||||||
|
command.WithFlag("--batch"),
|
||||||
|
)
|
||||||
|
cmd.Add(flags...)
|
||||||
err := cmd.Run(ctx,
|
err := cmd.Run(ctx,
|
||||||
command.WithDir(repoPath),
|
command.WithDir(repoPath),
|
||||||
command.WithStdin(batchStdinReader),
|
command.WithStdin(batchStdinReader),
|
||||||
@ -87,42 +91,48 @@ func CatFileBatch(
|
|||||||
return batchStdinWriter, batchReader, cancel
|
return batchStdinWriter, batchReader, cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BatchHeaderResponse struct {
|
||||||
|
SHA sha.SHA
|
||||||
|
Type string
|
||||||
|
Size int64
|
||||||
|
}
|
||||||
|
|
||||||
// ReadBatchHeaderLine reads the header line from cat-file --batch
|
// ReadBatchHeaderLine reads the header line from cat-file --batch
|
||||||
// We expect:
|
|
||||||
// <sha> SP <type> SP <size> LF
|
// <sha> SP <type> SP <size> LF
|
||||||
// sha is a 40byte not 20byte here.
|
// sha is a 40byte not 20byte here.
|
||||||
func ReadBatchHeaderLine(rd *bufio.Reader) (sha []byte, objType string, size int64, err error) {
|
func ReadBatchHeaderLine(rd *bufio.Reader) (*BatchHeaderResponse, error) {
|
||||||
objType, err = rd.ReadString('\n')
|
line, err := rd.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(objType) == 1 {
|
if len(line) == 1 {
|
||||||
objType, err = rd.ReadString('\n')
|
line, err = rd.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
idx := strings.IndexByte(objType, ' ')
|
idx := strings.IndexByte(line, ' ')
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
log.Debug().Msgf("missing space type: %s", objType)
|
return nil, errors.NotFound("missing space char for: %s", line)
|
||||||
err = errors.NotFound("sha '%s' not found", sha)
|
|
||||||
return nil, "", 0, err
|
|
||||||
}
|
}
|
||||||
sha = []byte(objType[:idx])
|
id := line[:idx]
|
||||||
objType = objType[idx+1:]
|
objType := line[idx+1:]
|
||||||
|
|
||||||
idx = strings.IndexByte(objType, ' ')
|
idx = strings.IndexByte(objType, ' ')
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
err = errors.NotFound("sha '%s' not found", sha)
|
return nil, errors.NotFound("sha '%s' not found", id)
|
||||||
return nil, "", 0, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sizeStr := objType[idx+1 : len(objType)-1]
|
sizeStr := objType[idx+1 : len(objType)-1]
|
||||||
objType = objType[:idx]
|
objType = objType[:idx]
|
||||||
|
|
||||||
size, err = strconv.ParseInt(sizeStr, 10, 64)
|
size, err := strconv.ParseInt(sizeStr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return sha, objType, size, nil
|
return &BatchHeaderResponse{
|
||||||
|
SHA: sha.Must(id),
|
||||||
|
Type: objType,
|
||||||
|
Size: size,
|
||||||
|
}, nil
|
||||||
}
|
}
|
@ -12,12 +12,14 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -26,54 +28,115 @@ import (
|
|||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git/command"
|
"github.com/harness/gitness/git/command"
|
||||||
"github.com/harness/gitness/git/enum"
|
"github.com/harness/gitness/git/enum"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/sha"
|
||||||
|
|
||||||
gitea "code.gitea.io/gitea/modules/git"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CommitGPGSignature represents a git commit signature part.
|
||||||
|
type CommitGPGSignature struct {
|
||||||
|
Signature string
|
||||||
|
Payload string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommitChangesOptions struct {
|
||||||
|
Committer Signature
|
||||||
|
Author Signature
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommitFileStats struct {
|
||||||
|
ChangeType enum.FileDiffStatus
|
||||||
|
Path string
|
||||||
|
OldPath string // populated only in case of renames
|
||||||
|
Insertions int64
|
||||||
|
Deletions int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Commit struct {
|
||||||
|
SHA sha.SHA `json:"sha"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Author Signature `json:"author"`
|
||||||
|
Committer Signature `json:"committer"`
|
||||||
|
Signature *CommitGPGSignature
|
||||||
|
ParentSHAs []sha.SHA
|
||||||
|
FileStats []CommitFileStats `json:"file_stats,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommitFilter struct {
|
||||||
|
Path string
|
||||||
|
AfterRef string
|
||||||
|
Since int64
|
||||||
|
Until int64
|
||||||
|
Committer string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitDivergenceRequest contains the refs for which the converging commits should be counted.
|
||||||
|
type CommitDivergenceRequest struct {
|
||||||
|
// From is the ref from which the counting of the diverging commits starts.
|
||||||
|
From string
|
||||||
|
// To is the ref at which the counting of the diverging commits ends.
|
||||||
|
To string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitDivergence contains the information of the count of converging commits between two refs.
|
||||||
|
type CommitDivergence struct {
|
||||||
|
// Ahead is the count of commits the 'From' ref is ahead of the 'To' ref.
|
||||||
|
Ahead int32
|
||||||
|
// Behind is the count of commits the 'From' ref is behind the 'To' ref.
|
||||||
|
Behind int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type PathRenameDetails struct {
|
||||||
|
OldPath string
|
||||||
|
Path string
|
||||||
|
CommitSHABefore sha.SHA
|
||||||
|
CommitSHAAfter sha.SHA
|
||||||
|
}
|
||||||
|
|
||||||
// GetLatestCommit gets the latest commit of a path relative from the provided revision.
|
// GetLatestCommit gets the latest commit of a path relative from the provided revision.
|
||||||
func (a Adapter) GetLatestCommit(
|
func (g *Git) GetLatestCommit(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
rev string,
|
rev string,
|
||||||
treePath string,
|
treePath string,
|
||||||
) (*types.Commit, error) {
|
) (*Commit, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return nil, ErrRepositoryPathEmpty
|
return nil, ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
treePath = cleanTreePath(treePath)
|
treePath = cleanTreePath(treePath)
|
||||||
|
|
||||||
return GetCommit(ctx, repoPath, rev, treePath)
|
return getCommit(ctx, repoPath, rev, treePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getGiteaCommits(
|
func getCommits(
|
||||||
giteaRepo *gitea.Repository,
|
ctx context.Context,
|
||||||
|
repoPath string,
|
||||||
commitIDs []string,
|
commitIDs []string,
|
||||||
) ([]*gitea.Commit, error) {
|
) ([]*Commit, error) {
|
||||||
var giteaCommits []*gitea.Commit
|
|
||||||
if len(commitIDs) == 0 {
|
if len(commitIDs) == 0 {
|
||||||
return giteaCommits, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
commits := make([]*Commit, 0, len(commitIDs))
|
||||||
for _, commitID := range commitIDs {
|
for _, commitID := range commitIDs {
|
||||||
commit, err := giteaRepo.GetCommit(commitID)
|
commit, err := getCommit(ctx, repoPath, commitID, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get commit '%s': %w", commitID, err)
|
return nil, fmt.Errorf("failed to get commit '%s': %w", commitID, err)
|
||||||
}
|
}
|
||||||
giteaCommits = append(giteaCommits, commit)
|
commits = append(commits, commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
return giteaCommits, nil
|
return commits, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Adapter) listCommitSHAs(
|
func (g *Git) listCommitSHAs(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
ref string,
|
ref string,
|
||||||
page int,
|
page int,
|
||||||
limit int,
|
limit int,
|
||||||
filter types.CommitFilter,
|
filter CommitFilter,
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
cmd := command.New("rev-list")
|
cmd := command.New("rev-list")
|
||||||
|
|
||||||
@ -115,7 +178,7 @@ func (a Adapter) listCommitSHAs(
|
|||||||
err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(output))
|
err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(output))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: handle error in case they don't have a common merge base!
|
// TODO: handle error in case they don't have a common merge base!
|
||||||
return nil, processGiteaErrorf(err, "failed to trigger rev-list command")
|
return nil, processGitErrorf(err, "failed to trigger rev-list command")
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseLinesToSlice(output.Bytes()), nil
|
return parseLinesToSlice(output.Bytes()), nil
|
||||||
@ -124,69 +187,55 @@ func (a Adapter) listCommitSHAs(
|
|||||||
// ListCommitSHAs lists the commits reachable from ref.
|
// ListCommitSHAs lists the commits reachable from ref.
|
||||||
// Note: ref & afterRef can be Branch / Tag / CommitSHA.
|
// Note: ref & afterRef can be Branch / Tag / CommitSHA.
|
||||||
// Note: commits returned are [ref->...->afterRef).
|
// Note: commits returned are [ref->...->afterRef).
|
||||||
func (a Adapter) ListCommitSHAs(
|
func (g *Git) ListCommitSHAs(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
ref string,
|
ref string,
|
||||||
page int,
|
page int,
|
||||||
limit int,
|
limit int,
|
||||||
filter types.CommitFilter,
|
filter CommitFilter,
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
return a.listCommitSHAs(ctx, repoPath, ref, page, limit, filter)
|
return g.listCommitSHAs(ctx, repoPath, ref, page, limit, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListCommits lists the commits reachable from ref.
|
// ListCommits lists the commits reachable from ref.
|
||||||
// Note: ref & afterRef can be Branch / Tag / CommitSHA.
|
// Note: ref & afterRef can be Branch / Tag / CommitSHA.
|
||||||
// Note: commits returned are [ref->...->afterRef).
|
// Note: commits returned are [ref->...->afterRef).
|
||||||
func (a Adapter) ListCommits(
|
func (g *Git) ListCommits(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
ref string,
|
ref string,
|
||||||
page int,
|
page int,
|
||||||
limit int,
|
limit int,
|
||||||
includeStats bool,
|
includeStats bool,
|
||||||
filter types.CommitFilter,
|
filter CommitFilter,
|
||||||
) ([]types.Commit, []types.PathRenameDetails, error) {
|
) ([]*Commit, []PathRenameDetails, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return nil, nil, ErrRepositoryPathEmpty
|
return nil, nil, ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
giteaRepo, err := gitea.OpenRepository(ctx, repoPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, processGiteaErrorf(err, "failed to open repository")
|
|
||||||
}
|
|
||||||
defer giteaRepo.Close()
|
|
||||||
|
|
||||||
commitSHAs, err := a.listCommitSHAs(ctx, repoPath, ref, page, limit, filter)
|
commitSHAs, err := g.listCommitSHAs(ctx, repoPath, ref, page, limit, filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
giteaCommits, err := getGiteaCommits(giteaRepo, commitSHAs)
|
commits, err := getCommits(ctx, repoPath, commitSHAs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
commits := make([]types.Commit, len(giteaCommits))
|
if includeStats {
|
||||||
for i := range giteaCommits {
|
for _, commit := range commits {
|
||||||
var commit *types.Commit
|
fileStats, err := getCommitFileStats(ctx, repoPath, commit.SHA)
|
||||||
commit, err = mapGiteaCommit(giteaCommits[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if includeStats {
|
|
||||||
fileStats, err := getCommitFileStats(ctx, giteaRepo, commit.SHA)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("encountered error getting commit file stats: %w", err)
|
return nil, nil, fmt.Errorf("encountered error getting commit file stats: %w", err)
|
||||||
}
|
}
|
||||||
commit.FileStats = fileStats
|
commit.FileStats = fileStats
|
||||||
}
|
}
|
||||||
|
|
||||||
commits[i] = *commit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(filter.Path) != 0 {
|
if len(filter.Path) != 0 {
|
||||||
renameDetailsList, err := getRenameDetails(ctx, giteaRepo, commits, filter.Path)
|
renameDetailsList, err := getRenameDetails(ctx, repoPath, commits, filter.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -199,49 +248,48 @@ func (a Adapter) ListCommits(
|
|||||||
|
|
||||||
func getCommitFileStats(
|
func getCommitFileStats(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
giteaRepo *gitea.Repository,
|
repoPath string,
|
||||||
sha string,
|
sha sha.SHA,
|
||||||
) ([]types.CommitFileStats, error) {
|
) ([]CommitFileStats, error) {
|
||||||
var changeInfoTypes map[string]changeInfoType
|
var changeInfoTypes map[string]changeInfoType
|
||||||
changeInfoTypes, err := getChangeInfoTypes(ctx, giteaRepo, sha)
|
changeInfoTypes, err := getChangeInfoTypes(ctx, repoPath, sha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get change infos: %w", err)
|
return nil, fmt.Errorf("failed to get change infos: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
changeInfoChanges, err := getChangeInfoChanges(giteaRepo, sha)
|
changeInfoChanges, err := getChangeInfoChanges(ctx, repoPath, sha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []types.CommitFileStats{}, fmt.Errorf("failed to get change infos: %w", err)
|
return []CommitFileStats{}, fmt.Errorf("failed to get change infos: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileStats := make([]types.CommitFileStats, len(changeInfoChanges))
|
fileStats := make([]CommitFileStats, len(changeInfoChanges))
|
||||||
i := 0
|
i := 0
|
||||||
for path, info := range changeInfoChanges {
|
for path, info := range changeInfoChanges {
|
||||||
fileStats[i] = types.CommitFileStats{
|
fileStats[i] = CommitFileStats{
|
||||||
Path: changeInfoTypes[path].Path,
|
Path: changeInfoTypes[path].Path,
|
||||||
OldPath: changeInfoTypes[path].OldPath,
|
OldPath: changeInfoTypes[path].OldPath,
|
||||||
Status: changeInfoTypes[path].Status,
|
ChangeType: changeInfoTypes[path].Status,
|
||||||
Insertions: info.Insertions,
|
Insertions: info.Insertions,
|
||||||
Deletions: info.Deletions,
|
Deletions: info.Deletions,
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileStats, nil
|
return fileStats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case of rename of a file, same commit will be listed twice - Once in old file and second time in new file.
|
// In case of rename of a file, same commit will be listed twice - Once in old file and second time in new file.
|
||||||
// Hence, we are making it a pattern to only list it as part of new file and not as part of old file.
|
// Hence, we are making it a pattern to only list it as part of new file and not as part of old file.
|
||||||
func cleanupCommitsForRename(
|
func cleanupCommitsForRename(
|
||||||
commits []types.Commit,
|
commits []*Commit,
|
||||||
renameDetails []types.PathRenameDetails,
|
renameDetails []PathRenameDetails,
|
||||||
path string,
|
path string,
|
||||||
) []types.Commit {
|
) []*Commit {
|
||||||
if len(commits) == 0 {
|
if len(commits) == 0 {
|
||||||
return commits
|
return commits
|
||||||
}
|
}
|
||||||
for _, renameDetail := range renameDetails {
|
for _, renameDetail := range renameDetails {
|
||||||
// Since rename details is present it implies that we have commits and hence don't need null check.
|
// Since rename details is present it implies that we have commits and hence don't need null check.
|
||||||
if commits[0].SHA == renameDetail.CommitSHABefore && path == renameDetail.OldPath {
|
if commits[0].SHA.Equal(renameDetail.CommitSHABefore) && path == renameDetail.OldPath {
|
||||||
return commits[1:]
|
return commits[1:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,17 +298,17 @@ func cleanupCommitsForRename(
|
|||||||
|
|
||||||
func getRenameDetails(
|
func getRenameDetails(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
giteaRepo *gitea.Repository,
|
repoPath string,
|
||||||
commits []types.Commit,
|
commits []*Commit,
|
||||||
path string,
|
path string,
|
||||||
) ([]types.PathRenameDetails, error) {
|
) ([]PathRenameDetails, error) {
|
||||||
if len(commits) == 0 {
|
if len(commits) == 0 {
|
||||||
return []types.PathRenameDetails{}, nil
|
return []PathRenameDetails{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
renameDetailsList := make([]types.PathRenameDetails, 0, 2)
|
renameDetailsList := make([]PathRenameDetails, 0, 2)
|
||||||
|
|
||||||
renameDetails, err := giteaGetRenameDetails(ctx, giteaRepo, commits[0].SHA, path)
|
renameDetails, err := gitGetRenameDetails(ctx, repoPath, commits[0].SHA, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -273,7 +321,7 @@ func getRenameDetails(
|
|||||||
return renameDetailsList, nil
|
return renameDetailsList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
renameDetailsLast, err := giteaGetRenameDetails(ctx, giteaRepo, commits[len(commits)-1].SHA, path)
|
renameDetailsLast, err := gitGetRenameDetails(ctx, repoPath, commits[len(commits)-1].SHA, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -285,52 +333,56 @@ func getRenameDetails(
|
|||||||
return renameDetailsList, nil
|
return renameDetailsList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func giteaGetRenameDetails(
|
func gitGetRenameDetails(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
giteaRepo *gitea.Repository,
|
repoPath string,
|
||||||
ref string,
|
sha sha.SHA,
|
||||||
path string,
|
path string,
|
||||||
) (*types.PathRenameDetails, error) {
|
) (*PathRenameDetails, error) {
|
||||||
changeInfos, err := getChangeInfoTypes(ctx, giteaRepo, ref)
|
changeInfos, err := getChangeInfoTypes(ctx, repoPath, sha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &types.PathRenameDetails{}, fmt.Errorf("failed to get change infos %w", err)
|
return &PathRenameDetails{}, fmt.Errorf("failed to get change infos %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range changeInfos {
|
for _, c := range changeInfos {
|
||||||
if c.Status == enum.FileDiffStatusRenamed && (c.OldPath == path || c.Path == path) {
|
if c.Status == enum.FileDiffStatusRenamed && (c.OldPath == path || c.Path == path) {
|
||||||
return &types.PathRenameDetails{
|
return &PathRenameDetails{
|
||||||
OldPath: c.OldPath,
|
OldPath: c.OldPath,
|
||||||
Path: c.Path,
|
Path: c.Path,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &types.PathRenameDetails{}, nil
|
return &PathRenameDetails{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gitLogNameStatus(giteaRepo *gitea.Repository, ref string) ([]string, error) {
|
func gitLogNameStatus(ctx context.Context, repoPath string, sha sha.SHA) ([]string, error) {
|
||||||
cmd := command.New("log",
|
cmd := command.New("log",
|
||||||
command.WithFlag("--name-status"),
|
command.WithFlag("--name-status"),
|
||||||
command.WithFlag("--format="),
|
command.WithFlag("--format="),
|
||||||
command.WithFlag("--max-count=1"),
|
command.WithFlag("--max-count=1"),
|
||||||
command.WithArg(ref),
|
command.WithArg(sha.String()),
|
||||||
)
|
)
|
||||||
output := &bytes.Buffer{}
|
output := &bytes.Buffer{}
|
||||||
err := cmd.Run(giteaRepo.Ctx, command.WithDir(giteaRepo.Path), command.WithStdout(output))
|
err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(output))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to trigger log command: %w", err)
|
return nil, fmt.Errorf("failed to trigger log command: %w", err)
|
||||||
}
|
}
|
||||||
return parseLinesToSlice(output.Bytes()), nil
|
return parseLinesToSlice(output.Bytes()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gitShowNumstat(giteaRepo *gitea.Repository, ref string) ([]string, error) {
|
func gitShowNumstat(
|
||||||
|
ctx context.Context,
|
||||||
|
repoPath string,
|
||||||
|
sha sha.SHA,
|
||||||
|
) ([]string, error) {
|
||||||
cmd := command.New("show",
|
cmd := command.New("show",
|
||||||
command.WithFlag("--numstat"),
|
command.WithFlag("--numstat"),
|
||||||
command.WithFlag("--format="),
|
command.WithFlag("--format="),
|
||||||
command.WithArg(ref),
|
command.WithArg(sha.String()),
|
||||||
)
|
)
|
||||||
output := &bytes.Buffer{}
|
output := &bytes.Buffer{}
|
||||||
err := cmd.Run(giteaRepo.Ctx, command.WithDir(giteaRepo.Path), command.WithStdout(output))
|
err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(output))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to trigger show command: %w", err)
|
return nil, fmt.Errorf("failed to trigger show command: %w", err)
|
||||||
}
|
}
|
||||||
@ -343,10 +395,10 @@ var renameRegex = regexp.MustCompile(`\t(.+)\t(.+)`)
|
|||||||
|
|
||||||
func getChangeInfoTypes(
|
func getChangeInfoTypes(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
giteaRepo *gitea.Repository,
|
repoPath string,
|
||||||
ref string,
|
sha sha.SHA,
|
||||||
) (map[string]changeInfoType, error) {
|
) (map[string]changeInfoType, error) {
|
||||||
lines, err := gitLogNameStatus(giteaRepo, ref)
|
lines, err := gitLogNameStatus(ctx, repoPath, sha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -382,10 +434,11 @@ var insertionsDeletionsRegex = regexp.MustCompile(`(\d+|-)\t(\d+|-)\t(.+)`)
|
|||||||
var renameRegexWithArrow = regexp.MustCompile(`\d+\t\d+\t.+\s=>\s(.+)`)
|
var renameRegexWithArrow = regexp.MustCompile(`\d+\t\d+\t.+\s=>\s(.+)`)
|
||||||
|
|
||||||
func getChangeInfoChanges(
|
func getChangeInfoChanges(
|
||||||
giteaRepo *gitea.Repository,
|
ctx context.Context,
|
||||||
ref string,
|
repoPath string,
|
||||||
|
sha sha.SHA,
|
||||||
) (map[string]changeInfoChange, error) {
|
) (map[string]changeInfoChange, error) {
|
||||||
lines, err := gitShowNumstat(giteaRepo, ref)
|
lines, err := gitShowNumstat(ctx, repoPath, sha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -457,25 +510,25 @@ func convertFileDiffStatus(ctx context.Context, c string) enum.FileDiffStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetCommit returns the (latest) commit for a specific revision.
|
// GetCommit returns the (latest) commit for a specific revision.
|
||||||
func (a Adapter) GetCommit(
|
func (g *Git) GetCommit(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
rev string,
|
rev string,
|
||||||
) (*types.Commit, error) {
|
) (*Commit, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return nil, ErrRepositoryPathEmpty
|
return nil, ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetCommit(ctx, repoPath, rev, "")
|
return getCommit(ctx, repoPath, rev, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Adapter) GetFullCommitID(
|
func (g *Git) GetFullCommitID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
shortID string,
|
shortID string,
|
||||||
) (string, error) {
|
) (sha.SHA, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return "", ErrRepositoryPathEmpty
|
return sha.None, ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
cmd := command.New("rev-parse",
|
cmd := command.New("rev-parse",
|
||||||
command.WithArg(shortID),
|
command.WithArg(shortID),
|
||||||
@ -484,66 +537,45 @@ func (a Adapter) GetFullCommitID(
|
|||||||
err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(output))
|
err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(output))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "exit status 128") {
|
if strings.Contains(err.Error(), "exit status 128") {
|
||||||
return "", errors.NotFound("commit not found %s", shortID)
|
return sha.None, errors.NotFound("commit not found %s", shortID)
|
||||||
}
|
}
|
||||||
return "", err
|
return sha.None, err
|
||||||
}
|
}
|
||||||
return strings.TrimSpace(output.String()), nil
|
return sha.New(output.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommits returns the (latest) commits for a specific list of refs.
|
// GetCommits returns the (latest) commits for a specific list of refs.
|
||||||
// Note: ref can be Branch / Tag / CommitSHA.
|
// Note: ref can be Branch / Tag / CommitSHA.
|
||||||
func (a Adapter) GetCommits(
|
func (g *Git) GetCommits(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
refs []string,
|
refs []string,
|
||||||
) ([]types.Commit, error) {
|
) ([]*Commit, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return nil, ErrRepositoryPathEmpty
|
return nil, ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
giteaRepo, err := gitea.OpenRepository(ctx, repoPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, processGiteaErrorf(err, "failed to open repository")
|
|
||||||
}
|
|
||||||
defer giteaRepo.Close()
|
|
||||||
|
|
||||||
commits := make([]types.Commit, len(refs))
|
return getCommits(ctx, repoPath, refs)
|
||||||
for i, sha := range refs {
|
|
||||||
var giteaCommit *gitea.Commit
|
|
||||||
giteaCommit, err = giteaRepo.GetCommit(sha)
|
|
||||||
if err != nil {
|
|
||||||
return nil, processGiteaErrorf(err, "error getting commit '%s'", sha)
|
|
||||||
}
|
|
||||||
|
|
||||||
var commit *types.Commit
|
|
||||||
commit, err = mapGiteaCommit(giteaCommit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
commits[i] = *commit
|
|
||||||
}
|
|
||||||
|
|
||||||
return commits, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommitDivergences returns the count of the diverging commits for all branch pairs.
|
// GetCommitDivergences returns the count of the diverging commits for all branch pairs.
|
||||||
// IMPORTANT: If a max is provided it limits the overal count of diverging commits
|
// IMPORTANT: If a max is provided it limits the overal count of diverging commits
|
||||||
// (max 10 could lead to (0, 10) while it's actually (2, 12)).
|
// (max 10 could lead to (0, 10) while it's actually (2, 12)).
|
||||||
func (a Adapter) GetCommitDivergences(
|
func (g *Git) GetCommitDivergences(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
requests []types.CommitDivergenceRequest,
|
requests []CommitDivergenceRequest,
|
||||||
max int32,
|
max int32,
|
||||||
) ([]types.CommitDivergence, error) {
|
) ([]CommitDivergence, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return nil, ErrRepositoryPathEmpty
|
return nil, ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
res := make([]types.CommitDivergence, len(requests))
|
res := make([]CommitDivergence, len(requests))
|
||||||
for i, req := range requests {
|
for i, req := range requests {
|
||||||
res[i], err = a.getCommitDivergence(ctx, repoPath, req, max)
|
res[i], err = g.getCommitDivergence(ctx, repoPath, req, max)
|
||||||
if types.IsNotFoundError(err) {
|
if errors.IsNotFound(err) {
|
||||||
res[i] = types.CommitDivergence{Ahead: -1, Behind: -1}
|
res[i] = CommitDivergence{Ahead: -1, Behind: -1}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -555,42 +587,37 @@ func (a Adapter) GetCommitDivergences(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getCommitDivergence returns the count of diverging commits for a pair of branches.
|
// getCommitDivergence returns the count of diverging commits for a pair of branches.
|
||||||
// IMPORTANT: If a max is provided it limits the overal count of diverging commits
|
// IMPORTANT: If a max is provided it limits the overall count of diverging commits
|
||||||
// (max 10 could lead to (0, 10) while it's actually (2, 12)).
|
// (max 10 could lead to (0, 10) while it's actually (2, 12)).
|
||||||
// NOTE: Gitea implementation makes two git cli calls, but it can be done with one
|
func (g *Git) getCommitDivergence(
|
||||||
// (downside is the max behavior explained above).
|
|
||||||
func (a Adapter) getCommitDivergence(
|
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
req types.CommitDivergenceRequest,
|
req CommitDivergenceRequest,
|
||||||
max int32,
|
max int32,
|
||||||
) (types.CommitDivergence, error) {
|
) (CommitDivergence, error) {
|
||||||
// prepare args
|
cmd := command.New("rev-list",
|
||||||
args := []string{
|
command.WithFlag("--count"),
|
||||||
"rev-list",
|
command.WithFlag("--left-right"),
|
||||||
"--count",
|
)
|
||||||
"--left-right",
|
|
||||||
}
|
|
||||||
// limit count if requested.
|
// limit count if requested.
|
||||||
if max > 0 {
|
if max > 0 {
|
||||||
args = append(args, "--max-count")
|
cmd.Add(command.WithFlag("--max-count", strconv.Itoa(int(max))))
|
||||||
args = append(args, fmt.Sprint(max))
|
|
||||||
}
|
}
|
||||||
// add query to get commits without shared base commits
|
// add query to get commits without shared base commits
|
||||||
args = append(args, fmt.Sprintf("%s...%s", req.From, req.To))
|
cmd.Add(command.WithArg(req.From + "..." + req.To))
|
||||||
|
|
||||||
var err error
|
stdout := &bytes.Buffer{}
|
||||||
cmd := gitea.NewCommand(ctx, args...)
|
err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(stdout))
|
||||||
stdOut, stdErr, err := cmd.RunStdString(&gitea.RunOpts{Dir: repoPath})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.CommitDivergence{},
|
return CommitDivergence{},
|
||||||
processGiteaErrorf(err, "git rev-list failed for '%s...%s' (stdErr: '%s')", req.From, req.To, stdErr)
|
processGitErrorf(err, "git rev-list failed for '%s...%s'", req.From, req.To)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse output, e.g.: `1 2\n`
|
// parse output, e.g.: `1 2\n`
|
||||||
rawLeft, rawRight, ok := strings.Cut(stdOut, "\t")
|
output := stdout.String()
|
||||||
|
rawLeft, rawRight, ok := strings.Cut(output, "\t")
|
||||||
if !ok {
|
if !ok {
|
||||||
return types.CommitDivergence{}, fmt.Errorf("git rev-list returned unexpected output '%s'", stdOut)
|
return CommitDivergence{}, fmt.Errorf("git rev-list returned unexpected output '%s'", output)
|
||||||
}
|
}
|
||||||
|
|
||||||
// trim any unnecessary characters
|
// trim any unnecessary characters
|
||||||
@ -600,16 +627,18 @@ func (a Adapter) getCommitDivergence(
|
|||||||
// parse numbers
|
// parse numbers
|
||||||
left, err := strconv.ParseInt(rawLeft, 10, 32)
|
left, err := strconv.ParseInt(rawLeft, 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.CommitDivergence{},
|
return CommitDivergence{},
|
||||||
fmt.Errorf("failed to parse git rev-list output for ahead '%s' (full: '%s')): %w", rawLeft, stdOut, err)
|
fmt.Errorf("failed to parse git rev-list output for ahead '%s' (full: '%s')): %w",
|
||||||
|
rawLeft, output, err)
|
||||||
}
|
}
|
||||||
right, err := strconv.ParseInt(rawRight, 10, 32)
|
right, err := strconv.ParseInt(rawRight, 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.CommitDivergence{},
|
return CommitDivergence{},
|
||||||
fmt.Errorf("failed to parse git rev-list output for behind '%s' (full: '%s')): %w", rawRight, stdOut, err)
|
fmt.Errorf("failed to parse git rev-list output for behind '%s' (full: '%s')): %w",
|
||||||
|
rawRight, output, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return types.CommitDivergence{
|
return CommitDivergence{
|
||||||
Ahead: int32(left),
|
Ahead: int32(left),
|
||||||
Behind: int32(right),
|
Behind: int32(right),
|
||||||
}, nil
|
}, nil
|
||||||
@ -630,14 +659,14 @@ func parseLinesToSlice(output []byte) []string {
|
|||||||
return slice
|
return slice
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommit returns info about a commit.
|
// getCommit returns info about a commit.
|
||||||
// TODO: Move this function outside of the adapter package.
|
// TODO: This function is used only for last used cache
|
||||||
func GetCommit(
|
func getCommit(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
rev string,
|
rev string,
|
||||||
path string,
|
path string,
|
||||||
) (*types.Commit, error) {
|
) (*Commit, error) {
|
||||||
const format = "" +
|
const format = "" +
|
||||||
fmtCommitHash + fmtZero + // 0
|
fmtCommitHash + fmtZero + // 0
|
||||||
fmtParentHashes + fmtZero + // 1
|
fmtParentHashes + fmtZero + // 1
|
||||||
@ -681,10 +710,12 @@ func GetCommit(
|
|||||||
"unexpected git log formatted output, expected %d, but got %d columns", columnCount, len(commitData))
|
"unexpected git log formatted output, expected %d, but got %d columns", columnCount, len(commitData))
|
||||||
}
|
}
|
||||||
|
|
||||||
sha := commitData[0]
|
commitSHA := sha.Must(commitData[0])
|
||||||
var parentSHAs []string
|
var parentSHAs []sha.SHA
|
||||||
if commitData[1] != "" {
|
if commitData[1] != "" {
|
||||||
parentSHAs = strings.Split(commitData[1], " ")
|
for _, parentSHA := range strings.Split(commitData[1], " ") {
|
||||||
|
parentSHAs = append(parentSHAs, sha.Must(parentSHA))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
authorName := commitData[2]
|
authorName := commitData[2]
|
||||||
authorEmail := commitData[3]
|
authorEmail := commitData[3]
|
||||||
@ -698,20 +729,20 @@ func GetCommit(
|
|||||||
authorTime, _ := time.Parse(time.RFC3339Nano, authorTimestamp)
|
authorTime, _ := time.Parse(time.RFC3339Nano, authorTimestamp)
|
||||||
committerTime, _ := time.Parse(time.RFC3339Nano, committerTimestamp)
|
committerTime, _ := time.Parse(time.RFC3339Nano, committerTimestamp)
|
||||||
|
|
||||||
return &types.Commit{
|
return &Commit{
|
||||||
SHA: sha,
|
SHA: commitSHA,
|
||||||
ParentSHAs: parentSHAs,
|
ParentSHAs: parentSHAs,
|
||||||
Title: subject,
|
Title: subject,
|
||||||
Message: body,
|
Message: body,
|
||||||
Author: types.Signature{
|
Author: Signature{
|
||||||
Identity: types.Identity{
|
Identity: Identity{
|
||||||
Name: authorName,
|
Name: authorName,
|
||||||
Email: authorEmail,
|
Email: authorEmail,
|
||||||
},
|
},
|
||||||
When: authorTime,
|
When: authorTime,
|
||||||
},
|
},
|
||||||
Committer: types.Signature{
|
Committer: Signature{
|
||||||
Identity: types.Identity{
|
Identity: Identity{
|
||||||
Name: committerName,
|
Name: committerName,
|
||||||
Email: committerEmail,
|
Email: committerEmail,
|
||||||
},
|
},
|
||||||
@ -719,3 +750,173 @@ func GetCommit(
|
|||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCommit returns info about a commit.
|
||||||
|
// TODO: Move this function outside of the api package.
|
||||||
|
func GetCommit(
|
||||||
|
ctx context.Context,
|
||||||
|
repoPath string,
|
||||||
|
rev string,
|
||||||
|
) (*Commit, error) {
|
||||||
|
wr, rd, cancel := CatFileBatch(ctx, repoPath)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, _ = wr.Write([]byte(rev + "\n"))
|
||||||
|
|
||||||
|
return getCommitFromBatchReader(ctx, repoPath, rd, rev)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCommitFromBatchReader(
|
||||||
|
ctx context.Context,
|
||||||
|
repoPath string,
|
||||||
|
rd *bufio.Reader,
|
||||||
|
rev string,
|
||||||
|
) (*Commit, error) {
|
||||||
|
output, err := ReadBatchHeaderLine(rd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read cat-file header line: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch output.Type {
|
||||||
|
case "missing":
|
||||||
|
return nil, errors.NotFound("sha '%s' not found", output.SHA)
|
||||||
|
case "tag":
|
||||||
|
// then we need to parse the tag
|
||||||
|
// and load the commit
|
||||||
|
data, err := io.ReadAll(io.LimitReader(rd, output.Size))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read tag data: %w", err)
|
||||||
|
}
|
||||||
|
if _, err = rd.Discard(1); err != nil {
|
||||||
|
return nil, fmt.Errorf("tag reader Discard failed: %w", err)
|
||||||
|
}
|
||||||
|
tag, err := parseTagData(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse tag: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
commit, err := GetCommit(ctx, repoPath, tag.TargetSha.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch commit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return commit, nil
|
||||||
|
case "commit":
|
||||||
|
commit, err := CommitFromReader(output.SHA, io.LimitReader(rd, output.Size))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("faile to read commit from reader: %w", err)
|
||||||
|
}
|
||||||
|
if _, err = rd.Discard(1); err != nil {
|
||||||
|
return nil, fmt.Errorf("commit reader Discard failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return commit, nil
|
||||||
|
default:
|
||||||
|
log.Warn().Msgf("Unknown object type: %s", output.Type)
|
||||||
|
_, err = rd.Discard(int(output.Size) + 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reader Discard failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil, errors.NotFound("rev '%s' not found", rev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitFromReader will generate a Commit from a provided reader
|
||||||
|
// We need this to interpret commits from cat-file or cat-file --batch
|
||||||
|
//
|
||||||
|
// If used as part of a cat-file --batch stream you need to limit the reader to the correct size.
|
||||||
|
//
|
||||||
|
//nolint:gocognit,nestif
|
||||||
|
func CommitFromReader(commitSHA sha.SHA, reader io.Reader) (*Commit, error) {
|
||||||
|
commit := &Commit{
|
||||||
|
SHA: commitSHA,
|
||||||
|
Author: Signature{},
|
||||||
|
Committer: Signature{},
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadSB := new(strings.Builder)
|
||||||
|
signatureSB := new(strings.Builder)
|
||||||
|
messageSB := new(strings.Builder)
|
||||||
|
message := false
|
||||||
|
pgpsig := false
|
||||||
|
|
||||||
|
bufReader, ok := reader.(*bufio.Reader)
|
||||||
|
if !ok {
|
||||||
|
bufReader = bufio.NewReader(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
readLoop:
|
||||||
|
for {
|
||||||
|
line, err := bufReader.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
if message {
|
||||||
|
_, _ = messageSB.Write(line)
|
||||||
|
}
|
||||||
|
_, _ = payloadSB.Write(line)
|
||||||
|
break readLoop
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("error occurred while reading a line from buffer: %w", err)
|
||||||
|
}
|
||||||
|
if pgpsig {
|
||||||
|
if len(line) > 0 && line[0] == ' ' {
|
||||||
|
_, _ = signatureSB.Write(line[1:])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pgpsig = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !message {
|
||||||
|
// This is probably not correct but is copied from go-gits interpretation...
|
||||||
|
trimmed := bytes.TrimSpace(line)
|
||||||
|
if len(trimmed) == 0 {
|
||||||
|
message = true
|
||||||
|
_, _ = payloadSB.Write(line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
split := bytes.SplitN(trimmed, []byte{' '}, 2)
|
||||||
|
var data []byte
|
||||||
|
if len(split) > 1 {
|
||||||
|
data = split[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
switch string(split[0]) {
|
||||||
|
case "tree":
|
||||||
|
_, _ = payloadSB.Write(line)
|
||||||
|
case "parent":
|
||||||
|
commit.ParentSHAs = append(commit.ParentSHAs, sha.Must(string(data)))
|
||||||
|
_, _ = payloadSB.Write(line)
|
||||||
|
case "author":
|
||||||
|
commit.Author, err = DecodeSignature(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse author signature: %w", err)
|
||||||
|
}
|
||||||
|
_, _ = payloadSB.Write(line)
|
||||||
|
case "committer":
|
||||||
|
commit.Committer, err = DecodeSignature(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse committer signature: %w", err)
|
||||||
|
}
|
||||||
|
_, _ = payloadSB.Write(line)
|
||||||
|
case "gpgsig":
|
||||||
|
_, _ = signatureSB.Write(data)
|
||||||
|
_ = signatureSB.WriteByte('\n')
|
||||||
|
pgpsig = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, _ = messageSB.Write(line)
|
||||||
|
_, _ = payloadSB.Write(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commit.Message = messageSB.String()
|
||||||
|
commit.Signature = &CommitGPGSignature{
|
||||||
|
Signature: signatureSB.String(),
|
||||||
|
Payload: payloadSB.String(),
|
||||||
|
}
|
||||||
|
if len(commit.Signature.Signature) == 0 {
|
||||||
|
commit.Signature = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return commit, nil
|
||||||
|
}
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -24,7 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Config set local git key and value configuration.
|
// Config set local git key and value configuration.
|
||||||
func (a Adapter) Config(
|
func (g *Git) Config(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
key string,
|
key string,
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@ -21,23 +21,37 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git/command"
|
"github.com/harness/gitness/git/command"
|
||||||
"github.com/harness/gitness/git/parser"
|
"github.com/harness/gitness/git/parser"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/sha"
|
||||||
|
"github.com/harness/gitness/types"
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type FileDiffRequest struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
StartLine int `json:"start_line"`
|
||||||
|
EndLine int `json:"-"` // warning: changes are possible and this field may not exist in the future
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileDiffRequests []FileDiffRequest
|
||||||
|
|
||||||
|
type DiffShortStat struct {
|
||||||
|
Files int
|
||||||
|
Additions int
|
||||||
|
Deletions int
|
||||||
|
}
|
||||||
|
|
||||||
// modifyHeader needs to modify diff hunk header with the new start line
|
// modifyHeader needs to modify diff hunk header with the new start line
|
||||||
// and end line with calculated span.
|
// and end line with calculated span.
|
||||||
// if diff hunk header is -100, 50 +100, 50 and startLine = 120, endLine=140
|
// if diff hunk header is -100, 50 +100, 50 and startLine = 120, endLine=140
|
||||||
// then we need to modify header to -120,20 +120,20.
|
// then we need to modify header to -120,20 +120,20.
|
||||||
// warning: changes are possible and param endLine may not exist in the future.
|
// warning: changes are possible and param endLine may not exist in the future.
|
||||||
func modifyHeader(hunk types.HunkHeader, startLine, endLine int) []byte {
|
func modifyHeader(hunk parser.HunkHeader, startLine, endLine int) []byte {
|
||||||
oldStartLine := hunk.OldLine
|
oldStartLine := hunk.OldLine
|
||||||
newStartLine := hunk.NewLine
|
newStartLine := hunk.NewLine
|
||||||
oldSpan := hunk.OldSpan
|
oldSpan := hunk.OldSpan
|
||||||
@ -142,34 +156,42 @@ func cutLinesFromFullFileDiff(w io.Writer, r io.Reader, startLine, endLine int)
|
|||||||
return scanner.Err()
|
return scanner.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Adapter) RawDiff(
|
func (g *Git) RawDiff(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
w io.Writer,
|
w io.Writer,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
baseRef string,
|
baseRef string,
|
||||||
headRef string,
|
headRef string,
|
||||||
mergeBase bool,
|
mergeBase bool,
|
||||||
files ...types.FileDiffRequest,
|
alternates []string,
|
||||||
|
files ...FileDiffRequest,
|
||||||
) error {
|
) error {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return ErrRepositoryPathEmpty
|
return ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
baseTag, err := a.GetAnnotatedTag(ctx, repoPath, baseRef)
|
baseTag, err := g.GetAnnotatedTag(ctx, repoPath, baseRef)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
baseRef = baseTag.TargetSha
|
baseRef = baseTag.TargetSha.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
headTag, err := a.GetAnnotatedTag(ctx, repoPath, headRef)
|
headTag, err := g.GetAnnotatedTag(ctx, repoPath, headRef)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
headRef = headTag.TargetSha
|
headRef = headTag.TargetSha.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
args := make([]string, 0, 8)
|
cmd := command.New("diff",
|
||||||
args = append(args, "diff", "-M", "--full-index")
|
command.WithFlag("-M"),
|
||||||
|
command.WithFlag("--full-index"),
|
||||||
|
)
|
||||||
if mergeBase {
|
if mergeBase {
|
||||||
args = append(args, "--merge-base")
|
cmd.Add(command.WithFlag("--merge-base"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(alternates) > 0 {
|
||||||
|
cmd.Add(command.WithAlternateObjectDirs(alternates...))
|
||||||
|
}
|
||||||
|
|
||||||
perFileDiffRequired := false
|
perFileDiffRequired := false
|
||||||
paths := make([]string, 0, len(files))
|
paths := make([]string, 0, len(files))
|
||||||
if len(files) > 0 {
|
if len(files) > 0 {
|
||||||
@ -186,8 +208,8 @@ func (a Adapter) RawDiff(
|
|||||||
again:
|
again:
|
||||||
startLine := 0
|
startLine := 0
|
||||||
endLine := 0
|
endLine := 0
|
||||||
newargs := make([]string, len(args), len(args)+8)
|
|
||||||
copy(newargs, args)
|
newCmd := cmd.Clone()
|
||||||
|
|
||||||
if len(files) > 0 {
|
if len(files) > 0 {
|
||||||
startLine = files[processed].StartLine
|
startLine = files[processed].StartLine
|
||||||
@ -196,16 +218,15 @@ again:
|
|||||||
|
|
||||||
if perFileDiffRequired {
|
if perFileDiffRequired {
|
||||||
if startLine > 0 || endLine > 0 {
|
if startLine > 0 || endLine > 0 {
|
||||||
newargs = append(newargs, "-U"+strconv.Itoa(math.MaxInt32))
|
newCmd.Add(command.WithFlag("-U" + strconv.Itoa(math.MaxInt32)))
|
||||||
}
|
}
|
||||||
paths = []string{files[processed].Path}
|
paths = []string{files[processed].Path}
|
||||||
}
|
}
|
||||||
|
|
||||||
newargs = append(newargs, baseRef, headRef)
|
newCmd.Add(command.WithArg(baseRef, headRef))
|
||||||
|
|
||||||
if len(paths) > 0 {
|
if len(paths) > 0 {
|
||||||
newargs = append(newargs, "--")
|
newCmd.Add(command.WithPostSepArg(paths...))
|
||||||
newargs = append(newargs, paths...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pipeRead, pipeWrite := io.Pipe()
|
pipeRead, pipeWrite := io.Pipe()
|
||||||
@ -217,7 +238,12 @@ again:
|
|||||||
_ = pipeWrite.CloseWithError(err)
|
_ = pipeWrite.CloseWithError(err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = a.rawDiff(ctx, pipeWrite, repoPath, baseRef, headRef, newargs...)
|
if err = newCmd.Run(ctx,
|
||||||
|
command.WithDir(repoPath),
|
||||||
|
command.WithStdout(pipeWrite),
|
||||||
|
); err != nil {
|
||||||
|
err = processGitErrorf(err, "git diff failed between %q and %q", baseRef, headRef)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err = cutLinesFromFullFileDiff(w, pipeRead, startLine, endLine); err != nil {
|
if err = cutLinesFromFullFileDiff(w, pipeRead, startLine, endLine); err != nil {
|
||||||
@ -234,67 +260,44 @@ again:
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Adapter) rawDiff(
|
|
||||||
ctx context.Context,
|
|
||||||
w io.Writer,
|
|
||||||
repoPath string,
|
|
||||||
baseRef string,
|
|
||||||
headRef string,
|
|
||||||
args ...string,
|
|
||||||
) error {
|
|
||||||
cmd := git.NewCommand(ctx, args...)
|
|
||||||
cmd.SetDescription(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath))
|
|
||||||
errbuf := bytes.Buffer{}
|
|
||||||
if err := cmd.Run(&git.RunOpts{
|
|
||||||
Dir: repoPath,
|
|
||||||
Stderr: &errbuf,
|
|
||||||
Stdout: w,
|
|
||||||
}); err != nil {
|
|
||||||
if errbuf.Len() > 0 {
|
|
||||||
err = &runStdError{err: err, stderr: errbuf.String()}
|
|
||||||
}
|
|
||||||
return processGiteaErrorf(err, "git diff failed between %q and %q", baseRef, headRef)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommitDiff will stream diff for provided ref.
|
// CommitDiff will stream diff for provided ref.
|
||||||
func (a Adapter) CommitDiff(
|
func (g *Git) CommitDiff(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
sha string,
|
rev string,
|
||||||
w io.Writer,
|
w io.Writer,
|
||||||
) error {
|
) error {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return ErrRepositoryPathEmpty
|
return ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
if sha == "" {
|
if rev == "" {
|
||||||
return errors.InvalidArgument("commit sha cannot be empty")
|
return errors.InvalidArgument("git revision cannot be empty")
|
||||||
}
|
}
|
||||||
args := make([]string, 0, 8)
|
|
||||||
args = append(args, "show", "--full-index", "--pretty=format:%b", sha)
|
|
||||||
|
|
||||||
stderr := new(bytes.Buffer)
|
cmd := command.New("show",
|
||||||
cmd := git.NewCommand(ctx, args...)
|
command.WithFlag("--full-index"),
|
||||||
if err := cmd.Run(&git.RunOpts{
|
command.WithFlag("--pretty=format:%b"),
|
||||||
Dir: repoPath,
|
command.WithArg(rev),
|
||||||
Stdout: w,
|
)
|
||||||
Stderr: stderr,
|
|
||||||
}); err != nil {
|
if err := cmd.Run(ctx,
|
||||||
return processGiteaErrorf(err, "commit diff error: %v", stderr)
|
command.WithDir(repoPath),
|
||||||
|
command.WithStdout(w),
|
||||||
|
); err != nil {
|
||||||
|
return processGitErrorf(err, "commit diff error")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Adapter) DiffShortStat(
|
func (g *Git) DiffShortStat(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
baseRef string,
|
baseRef string,
|
||||||
headRef string,
|
headRef string,
|
||||||
useMergeBase bool,
|
useMergeBase bool,
|
||||||
) (types.DiffShortStat, error) {
|
) (DiffShortStat, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return types.DiffShortStat{}, ErrRepositoryPathEmpty
|
return DiffShortStat{}, ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
separator := ".."
|
separator := ".."
|
||||||
if useMergeBase {
|
if useMergeBase {
|
||||||
@ -302,31 +305,27 @@ func (a Adapter) DiffShortStat(
|
|||||||
}
|
}
|
||||||
|
|
||||||
shortstatArgs := []string{baseRef + separator + headRef}
|
shortstatArgs := []string{baseRef + separator + headRef}
|
||||||
if len(baseRef) == 0 || baseRef == git.EmptySHA {
|
if len(baseRef) == 0 || baseRef == types.NilSHA {
|
||||||
shortstatArgs = []string{git.EmptyTreeSHA, headRef}
|
shortstatArgs = []string{sha.EmptyTree, headRef}
|
||||||
}
|
}
|
||||||
numFiles, totalAdditions, totalDeletions, err := git.GetDiffShortStat(ctx, repoPath, shortstatArgs...)
|
stat, err := GetDiffShortStat(ctx, repoPath, shortstatArgs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.DiffShortStat{}, processGiteaErrorf(err, "failed to get diff short stat between %s and %s",
|
return DiffShortStat{}, processGitErrorf(err, "failed to get diff short stat between %s and %s",
|
||||||
baseRef, headRef)
|
baseRef, headRef)
|
||||||
}
|
}
|
||||||
return types.DiffShortStat{
|
return stat, nil
|
||||||
Files: numFiles,
|
|
||||||
Additions: totalAdditions,
|
|
||||||
Deletions: totalDeletions,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDiffHunkHeaders for each file in diff output returns file name (old and new to detect renames),
|
// GetDiffHunkHeaders for each file in diff output returns file name (old and new to detect renames),
|
||||||
// and all hunk headers. The diffs are generated with unified=0 parameter to create minimum sized hunks.
|
// and all hunk headers. The diffs are generated with unified=0 parameter to create minimum sized hunks.
|
||||||
// Hunks' body is ignored.
|
// Hunks' body is ignored.
|
||||||
// The purpose of this function is to get data based on which code comments could be repositioned.
|
// The purpose of this function is to get data based on which code comments could be repositioned.
|
||||||
func (a Adapter) GetDiffHunkHeaders(
|
func (g *Git) GetDiffHunkHeaders(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
targetRef string,
|
targetRef string,
|
||||||
sourceRef string,
|
sourceRef string,
|
||||||
) ([]*types.DiffFileHunkHeaders, error) {
|
) ([]*parser.DiffFileHunkHeaders, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return nil, ErrRepositoryPathEmpty
|
return nil, ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
@ -340,13 +339,18 @@ func (a Adapter) GetDiffHunkHeaders(
|
|||||||
_ = pipeWrite.CloseWithError(err)
|
_ = pipeWrite.CloseWithError(err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
cmd := git.NewCommand(ctx,
|
cmd := command.New("diff",
|
||||||
"diff", "--patch", "--no-color", "--unified=0", sourceRef, targetRef)
|
command.WithFlag("--patch"),
|
||||||
err = cmd.Run(&git.RunOpts{
|
command.WithFlag("--no-color"),
|
||||||
Dir: repoPath,
|
command.WithFlag("--unified=0"),
|
||||||
Stdout: pipeWrite,
|
command.WithArg(sourceRef),
|
||||||
Stderr: stderr, // We capture stderr output in a buffer.
|
command.WithArg(targetRef),
|
||||||
})
|
)
|
||||||
|
err = cmd.Run(ctx,
|
||||||
|
command.WithDir(repoPath),
|
||||||
|
command.WithStdout(pipeWrite),
|
||||||
|
command.WithStderr(stderr), // We capture stderr output in a buffer.
|
||||||
|
)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
fileHunkHeaders, err := parser.GetHunkHeaders(pipeRead)
|
fileHunkHeaders, err := parser.GetHunkHeaders(pipeRead)
|
||||||
@ -368,16 +372,16 @@ func (a Adapter) GetDiffHunkHeaders(
|
|||||||
// The purpose of this function is to get diff data with which code comments could be generated.
|
// The purpose of this function is to get diff data with which code comments could be generated.
|
||||||
//
|
//
|
||||||
//nolint:gocognit
|
//nolint:gocognit
|
||||||
func (a Adapter) DiffCut(
|
func (g *Git) DiffCut(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
targetRef string,
|
targetRef string,
|
||||||
sourceRef string,
|
sourceRef string,
|
||||||
path string,
|
path string,
|
||||||
params types.DiffCutParams,
|
params parser.DiffCutParams,
|
||||||
) (types.HunkHeader, types.Hunk, error) {
|
) (parser.HunkHeader, parser.Hunk, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return types.HunkHeader{}, types.Hunk{}, ErrRepositoryPathEmpty
|
return parser.HunkHeader{}, parser.Hunk{}, ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
// first fetch the list of the changed files
|
// first fetch the list of the changed files
|
||||||
@ -403,7 +407,7 @@ func (a Adapter) DiffCut(
|
|||||||
|
|
||||||
diffEntries, err := parser.DiffRaw(pipeRead)
|
diffEntries, err := parser.DiffRaw(pipeRead)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.HunkHeader{}, types.Hunk{}, fmt.Errorf("failed to find the list of changed files: %w", err)
|
return parser.HunkHeader{}, parser.Hunk{}, fmt.Errorf("failed to find the list of changed files: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -420,11 +424,11 @@ func (a Adapter) DiffCut(
|
|||||||
|
|
||||||
if params.LineStartNew && path == entry.OldPath {
|
if params.LineStartNew && path == entry.OldPath {
|
||||||
msg := "for renamed files provide the new file name if commenting the changed lines"
|
msg := "for renamed files provide the new file name if commenting the changed lines"
|
||||||
return types.HunkHeader{}, types.Hunk{}, errors.InvalidArgument(msg)
|
return parser.HunkHeader{}, parser.Hunk{}, errors.InvalidArgument(msg)
|
||||||
}
|
}
|
||||||
if !params.LineStartNew && path == entry.Path {
|
if !params.LineStartNew && path == entry.Path {
|
||||||
msg := "for renamed files provide the old file name if commenting the old lines"
|
msg := "for renamed files provide the old file name if commenting the old lines"
|
||||||
return types.HunkHeader{}, types.Hunk{}, errors.InvalidArgument(msg)
|
return parser.HunkHeader{}, parser.Hunk{}, errors.InvalidArgument(msg)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if entry.Path != path {
|
if entry.Path != path {
|
||||||
@ -448,7 +452,7 @@ func (a Adapter) DiffCut(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if newSHA == "" {
|
if newSHA == "" {
|
||||||
return types.HunkHeader{}, types.Hunk{}, errors.NotFound("file %s not found in the diff", path)
|
return parser.HunkHeader{}, parser.Hunk{}, errors.NotFound("file %s not found in the diff", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// next pull the diff cut for the requested file
|
// next pull the diff cut for the requested file
|
||||||
@ -484,35 +488,101 @@ func (a Adapter) DiffCut(
|
|||||||
diffCutHeader, linesHunk, err := parser.DiffCut(pipeRead, params)
|
diffCutHeader, linesHunk, err := parser.DiffCut(pipeRead, params)
|
||||||
if errStderr := parseDiffStderr(stderr); errStderr != nil {
|
if errStderr := parseDiffStderr(stderr); errStderr != nil {
|
||||||
// First check if there's something in the stderr buffer, if yes that's the error
|
// First check if there's something in the stderr buffer, if yes that's the error
|
||||||
return types.HunkHeader{}, types.Hunk{}, errStderr
|
return parser.HunkHeader{}, parser.Hunk{}, errStderr
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Next check if reading the git diff output caused an error
|
// Next check if reading the git diff output caused an error
|
||||||
return types.HunkHeader{}, types.Hunk{}, err
|
return parser.HunkHeader{}, parser.Hunk{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return diffCutHeader, linesHunk, nil
|
return diffCutHeader, linesHunk, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Adapter) DiffFileName(ctx context.Context,
|
func (g *Git) DiffFileName(ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
baseRef string,
|
baseRef string,
|
||||||
headRef string,
|
headRef string,
|
||||||
mergeBase bool,
|
mergeBase bool,
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
args := make([]string, 0, 8)
|
cmd := command.New("diff", command.WithFlag("--name-only"))
|
||||||
args = append(args, "diff", "--name-only")
|
|
||||||
if mergeBase {
|
if mergeBase {
|
||||||
args = append(args, "--merge-base")
|
cmd.Add(command.WithFlag("--merge-base"))
|
||||||
}
|
}
|
||||||
args = append(args, baseRef, headRef)
|
cmd.Add(command.WithArg(baseRef, headRef))
|
||||||
cmd := git.NewCommand(ctx, args...)
|
|
||||||
stdout, _, runErr := cmd.RunStdBytes(&git.RunOpts{Dir: repoPath})
|
stdout := &bytes.Buffer{}
|
||||||
if runErr != nil {
|
err := cmd.Run(ctx,
|
||||||
return nil, processGiteaErrorf(runErr, "failed to trigger diff command")
|
command.WithDir(repoPath),
|
||||||
|
command.WithStdout(stdout),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, processGitErrorf(err, "failed to trigger diff command")
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseLinesToSlice(stdout), nil
|
return parseLinesToSlice(stdout.Bytes()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDiffShortStat counts number of changed files, number of additions and deletions.
|
||||||
|
func GetDiffShortStat(
|
||||||
|
ctx context.Context,
|
||||||
|
repoPath string,
|
||||||
|
args ...string,
|
||||||
|
) (DiffShortStat, error) {
|
||||||
|
// Now if we call:
|
||||||
|
// $ git diff --shortstat 1ebb35b98889ff77299f24d82da426b434b0cca0...788b8b1440462d477f45b0088875
|
||||||
|
// we get:
|
||||||
|
// " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n"
|
||||||
|
|
||||||
|
cmd := command.New("diff",
|
||||||
|
command.WithFlag("--shortstat"),
|
||||||
|
command.WithArg(args...),
|
||||||
|
)
|
||||||
|
|
||||||
|
stdout := &bytes.Buffer{}
|
||||||
|
if err := cmd.Run(ctx,
|
||||||
|
command.WithDir(repoPath),
|
||||||
|
command.WithStdout(stdout),
|
||||||
|
); err != nil {
|
||||||
|
return DiffShortStat{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseDiffStat(stdout.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortStatFormat = regexp.MustCompile(
|
||||||
|
`\s*(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?`)
|
||||||
|
|
||||||
|
func parseDiffStat(stdout string) (stat DiffShortStat, err error) {
|
||||||
|
if len(stdout) == 0 || stdout == "\n" {
|
||||||
|
return DiffShortStat{}, nil
|
||||||
|
}
|
||||||
|
groups := shortStatFormat.FindStringSubmatch(stdout)
|
||||||
|
if len(groups) != 4 {
|
||||||
|
return DiffShortStat{}, fmt.Errorf("unable to parse shortstat: %s groups: %s", stdout, groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
stat.Files, err = strconv.Atoi(groups[1])
|
||||||
|
if err != nil {
|
||||||
|
return DiffShortStat{}, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumFiles %w",
|
||||||
|
stdout, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(groups[2]) != 0 {
|
||||||
|
stat.Additions, err = strconv.Atoi(groups[2])
|
||||||
|
if err != nil {
|
||||||
|
return DiffShortStat{}, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumAdditions %w",
|
||||||
|
stdout, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(groups[3]) != 0 {
|
||||||
|
stat.Deletions, err = strconv.Atoi(groups[3])
|
||||||
|
if err != nil {
|
||||||
|
return DiffShortStat{}, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumDeletions %w",
|
||||||
|
stdout, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stat, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDiffStderr(stderr *bytes.Buffer) error {
|
func parseDiffStderr(stderr *bytes.Buffer) error {
|
||||||
@ -528,7 +598,7 @@ func parseDiffStderr(stderr *bytes.Buffer) error {
|
|||||||
errRaw = strings.TrimPrefix(errRaw, "fatal: ") // git errors start with the "fatal: " prefix
|
errRaw = strings.TrimPrefix(errRaw, "fatal: ") // git errors start with the "fatal: " prefix
|
||||||
|
|
||||||
if strings.Contains(errRaw, "bad revision") {
|
if strings.Contains(errRaw, "bad revision") {
|
||||||
return types.ErrSHADoesNotMatch
|
return parser.ErrSHADoesNotMatch
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(errRaw)
|
return errors.New(errRaw)
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -21,14 +21,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/parser"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_modifyHeader(t *testing.T) {
|
func Test_modifyHeader(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
hunk types.HunkHeader
|
hunk parser.HunkHeader
|
||||||
startLine int
|
startLine int
|
||||||
endLine int
|
endLine int
|
||||||
}
|
}
|
||||||
@ -40,7 +40,7 @@ func Test_modifyHeader(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test empty",
|
name: "test empty",
|
||||||
args: args{
|
args: args{
|
||||||
hunk: types.HunkHeader{
|
hunk: parser.HunkHeader{
|
||||||
OldLine: 0,
|
OldLine: 0,
|
||||||
OldSpan: 0,
|
OldSpan: 0,
|
||||||
NewLine: 0,
|
NewLine: 0,
|
||||||
@ -54,7 +54,7 @@ func Test_modifyHeader(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test empty 1",
|
name: "test empty 1",
|
||||||
args: args{
|
args: args{
|
||||||
hunk: types.HunkHeader{
|
hunk: parser.HunkHeader{
|
||||||
OldLine: 0,
|
OldLine: 0,
|
||||||
OldSpan: 0,
|
OldSpan: 0,
|
||||||
NewLine: 0,
|
NewLine: 0,
|
||||||
@ -68,7 +68,7 @@ func Test_modifyHeader(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test empty old",
|
name: "test empty old",
|
||||||
args: args{
|
args: args{
|
||||||
hunk: types.HunkHeader{
|
hunk: parser.HunkHeader{
|
||||||
OldLine: 0,
|
OldLine: 0,
|
||||||
OldSpan: 0,
|
OldSpan: 0,
|
||||||
NewLine: 1,
|
NewLine: 1,
|
||||||
@ -82,7 +82,7 @@ func Test_modifyHeader(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test empty new",
|
name: "test empty new",
|
||||||
args: args{
|
args: args{
|
||||||
hunk: types.HunkHeader{
|
hunk: parser.HunkHeader{
|
||||||
OldLine: 1,
|
OldLine: 1,
|
||||||
OldSpan: 10,
|
OldSpan: 10,
|
||||||
NewLine: 0,
|
NewLine: 0,
|
||||||
@ -96,7 +96,7 @@ func Test_modifyHeader(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test 1",
|
name: "test 1",
|
||||||
args: args{
|
args: args{
|
||||||
hunk: types.HunkHeader{
|
hunk: parser.HunkHeader{
|
||||||
OldLine: 2,
|
OldLine: 2,
|
||||||
OldSpan: 20,
|
OldSpan: 20,
|
||||||
NewLine: 2,
|
NewLine: 2,
|
||||||
@ -110,7 +110,7 @@ func Test_modifyHeader(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test 2",
|
name: "test 2",
|
||||||
args: args{
|
args: args{
|
||||||
hunk: types.HunkHeader{
|
hunk: parser.HunkHeader{
|
||||||
OldLine: 2,
|
OldLine: 2,
|
||||||
OldSpan: 20,
|
OldSpan: 20,
|
||||||
NewLine: 2,
|
NewLine: 2,
|
||||||
@ -124,7 +124,7 @@ func Test_modifyHeader(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test 4",
|
name: "test 4",
|
||||||
args: args{
|
args: args{
|
||||||
hunk: types.HunkHeader{
|
hunk: parser.HunkHeader{
|
||||||
OldLine: 1,
|
OldLine: 1,
|
||||||
OldSpan: 10,
|
OldSpan: 10,
|
||||||
NewLine: 1,
|
NewLine: 1,
|
||||||
@ -138,7 +138,7 @@ func Test_modifyHeader(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test 5",
|
name: "test 5",
|
||||||
args: args{
|
args: args{
|
||||||
hunk: types.HunkHeader{
|
hunk: parser.HunkHeader{
|
||||||
OldLine: 1,
|
OldLine: 1,
|
||||||
OldSpan: 108,
|
OldSpan: 108,
|
||||||
NewLine: 1,
|
NewLine: 1,
|
162
git/api/errors.go
Normal file
162
git/api/errors.go
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
// Copyright 2023 Harness, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/errors"
|
||||||
|
"github.com/harness/gitness/git/enum"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidPath = errors.New("path is invalid")
|
||||||
|
ErrRepositoryPathEmpty = errors.InvalidArgument("repository path cannot be empty")
|
||||||
|
ErrBranchNameEmpty = errors.InvalidArgument("branch name cannot be empty")
|
||||||
|
ErrParseDiffHunkHeader = errors.Internal(nil, "failed to parse diff hunk header")
|
||||||
|
ErrNoDefaultBranch = errors.New("no default branch")
|
||||||
|
ErrInvalidSignature = errors.New("invalid signature")
|
||||||
|
)
|
||||||
|
|
||||||
|
// PushOutOfDateError represents an error if merging fails due to unrelated histories.
|
||||||
|
type PushOutOfDateError struct {
|
||||||
|
StdOut string
|
||||||
|
StdErr string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *PushOutOfDateError) Error() string {
|
||||||
|
return fmt.Sprintf("PushOutOfDate Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap unwraps the underlying error.
|
||||||
|
func (err *PushOutOfDateError) Unwrap() error {
|
||||||
|
return fmt.Errorf("%w - %s", err.Err, err.StdErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushRejectedError represents an error if merging fails due to rejection from a hook.
|
||||||
|
type PushRejectedError struct {
|
||||||
|
Message string
|
||||||
|
StdOut string
|
||||||
|
StdErr string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrPushRejected checks if an error is a PushRejectedError.
|
||||||
|
func IsErrPushRejected(err error) bool {
|
||||||
|
var errPushRejected *PushRejectedError
|
||||||
|
return errors.As(err, &errPushRejected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *PushRejectedError) Error() string {
|
||||||
|
return fmt.Sprintf("PushRejected Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap unwraps the underlying error.
|
||||||
|
func (err *PushRejectedError) Unwrap() error {
|
||||||
|
return fmt.Errorf("%w - %s", err.Err, err.StdErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateMessage generates the remote message from the stderr.
|
||||||
|
func (err *PushRejectedError) GenerateMessage() {
|
||||||
|
messageBuilder := &strings.Builder{}
|
||||||
|
i := strings.Index(err.StdErr, "remote: ")
|
||||||
|
if i < 0 {
|
||||||
|
err.Message = ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if len(err.StdErr) <= i+8 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err.StdErr[i:i+8] != "remote: " {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i += 8
|
||||||
|
nl := strings.IndexByte(err.StdErr[i:], '\n')
|
||||||
|
if nl >= 0 {
|
||||||
|
messageBuilder.WriteString(err.StdErr[i : i+nl+1])
|
||||||
|
i = i + nl + 1
|
||||||
|
} else {
|
||||||
|
messageBuilder.WriteString(err.StdErr[i:])
|
||||||
|
i = len(err.StdErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err.Message = strings.TrimSpace(messageBuilder.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoreThanOneError represents an error when there are more
|
||||||
|
// than one sources (branch, tag) with the same name.
|
||||||
|
type MoreThanOneError struct {
|
||||||
|
StdOut string
|
||||||
|
StdErr string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrMoreThanOne checks if an error is a MoreThanOneError.
|
||||||
|
func IsErrMoreThanOne(err error) bool {
|
||||||
|
var errMoreThanOne *MoreThanOneError
|
||||||
|
return errors.As(err, &errMoreThanOne)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *MoreThanOneError) Error() string {
|
||||||
|
return fmt.Sprintf("MoreThanOneError Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logs the error and message, returns either the provided message or a git equivalent if possible.
|
||||||
|
// Always logs the full message with error as warning.
|
||||||
|
func processGitErrorf(err error, format string, args ...interface{}) error {
|
||||||
|
// create fallback error returned if we can't map it
|
||||||
|
fallbackErr := errors.Internal(err, format, args...)
|
||||||
|
|
||||||
|
// always log internal error together with message.
|
||||||
|
log.Warn().Msgf("%v: [GIT] %v", fallbackErr, err)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err.Error() == "no such file or directory":
|
||||||
|
return errors.NotFound("repository not found")
|
||||||
|
default:
|
||||||
|
return fallbackErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeUnrelatedHistoriesError represents an error if merging fails due to unrelated histories.
|
||||||
|
type MergeUnrelatedHistoriesError struct {
|
||||||
|
Method enum.MergeMethod
|
||||||
|
StdOut string
|
||||||
|
StdErr string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsMergeUnrelatedHistoriesError(err error) bool {
|
||||||
|
return errors.Is(err, &MergeUnrelatedHistoriesError{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *MergeUnrelatedHistoriesError) Error() string {
|
||||||
|
return fmt.Sprintf("Merge UnrelatedHistories Error: %v: %s\n%s", e.Err, e.StdErr, e.StdOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *MergeUnrelatedHistoriesError) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:errorlint // the purpose of this method is to check whether the target itself if of this type.
|
||||||
|
func (e *MergeUnrelatedHistoriesError) Is(target error) bool {
|
||||||
|
_, ok := target.(*MergeUnrelatedHistoriesError)
|
||||||
|
return ok
|
||||||
|
}
|
@ -12,8 +12,22 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
const (
|
import (
|
||||||
gitTrace = "GIT_TRACE"
|
"path"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CleanUploadFileName Trims a filename and returns empty string if it is a .git directory.
|
||||||
|
func CleanUploadFileName(name string) string {
|
||||||
|
// Rebase the filename
|
||||||
|
name = strings.Trim(path.Clean("/"+name), "/")
|
||||||
|
// Git disallows any filenames to have a .git directory in them.
|
||||||
|
for _, part := range strings.Split(name, "/") {
|
||||||
|
if strings.ToLower(part) == ".git" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
94
git/api/foreachref/format.go
Normal file
94
git/api/foreachref/format.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// Copyright 2023 Harness, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package foreachref
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
nullChar = []byte("\x00")
|
||||||
|
dualNullChar = []byte("\x00\x00")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Format supports specifying and parsing an output format for 'git
|
||||||
|
// for-each-ref'. See See git-for-each-ref(1) for available fields.
|
||||||
|
type Format struct {
|
||||||
|
// fieldNames hold %(fieldname)s to be passed to the '--format' flag of
|
||||||
|
// for-each-ref. See git-for-each-ref(1) for available fields.
|
||||||
|
fieldNames []string
|
||||||
|
|
||||||
|
// fieldDelim is the character sequence that is used to separate fields
|
||||||
|
// for each reference. fieldDelim and refDelim should be selected to not
|
||||||
|
// interfere with each other and to not be present in field values.
|
||||||
|
fieldDelim []byte
|
||||||
|
// fieldDelimStr is a string representation of fieldDelim. Used to save
|
||||||
|
// us from repetitive reallocation whenever we need the delimiter as a
|
||||||
|
// string.
|
||||||
|
fieldDelimStr string
|
||||||
|
// refDelim is the character sequence used to separate reference from
|
||||||
|
// each other in the output. fieldDelim and refDelim should be selected
|
||||||
|
// to not interfere with each other and to not be present in field
|
||||||
|
// values.
|
||||||
|
refDelim []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFormat creates a forEachRefFormat using the specified fieldNames. See
|
||||||
|
// git-for-each-ref(1) for available fields.
|
||||||
|
func NewFormat(fieldNames ...string) Format {
|
||||||
|
return Format{
|
||||||
|
fieldNames: fieldNames,
|
||||||
|
fieldDelim: nullChar,
|
||||||
|
fieldDelimStr: string(nullChar),
|
||||||
|
refDelim: dualNullChar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag returns a for-each-ref --format flag value that captures the fieldNames.
|
||||||
|
func (f Format) Flag() string {
|
||||||
|
var formatFlag strings.Builder
|
||||||
|
for i, field := range f.fieldNames {
|
||||||
|
// field key and field value
|
||||||
|
formatFlag.WriteString(fmt.Sprintf("%s %%(%s)", field, field))
|
||||||
|
|
||||||
|
if i < len(f.fieldNames)-1 {
|
||||||
|
// note: escape delimiters to allow control characters as
|
||||||
|
// delimiters. For example, '%00' for null character or '%0a'
|
||||||
|
// for newline.
|
||||||
|
formatFlag.WriteString(f.hexEscaped(f.fieldDelim))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formatFlag.WriteString(f.hexEscaped(f.refDelim))
|
||||||
|
return formatFlag.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parser returns a Parser capable of parsing 'git for-each-ref' output produced
|
||||||
|
// with this Format.
|
||||||
|
func (f Format) Parser(r io.Reader) *Parser {
|
||||||
|
return NewParser(r, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hexEscaped produces hex-escpaed characters from a string. For example, "\n\0"
|
||||||
|
// would turn into "%0a%00".
|
||||||
|
func (f Format) hexEscaped(delim []byte) string {
|
||||||
|
escaped := ""
|
||||||
|
for i := 0; i < len(delim); i++ {
|
||||||
|
escaped += "%" + hex.EncodeToString([]byte{delim[i]})
|
||||||
|
}
|
||||||
|
return escaped
|
||||||
|
}
|
140
git/api/foreachref/parser.go
Normal file
140
git/api/foreachref/parser.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
// Copyright 2023 Harness, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package foreachref
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parser parses 'git for-each-ref' output according to a given output Format.
|
||||||
|
type Parser struct {
|
||||||
|
// tokenizes 'git for-each-ref' output into "reference paragraphs".
|
||||||
|
scanner *bufio.Scanner
|
||||||
|
|
||||||
|
// format represents the '--format' string that describes the expected
|
||||||
|
// 'git for-each-ref' output structure.
|
||||||
|
format Format
|
||||||
|
|
||||||
|
// err holds the last encountered error during parsing.
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a 'git for-each-ref' output parser that will parse all
|
||||||
|
// references in the provided Reader. The references in the output are assumed
|
||||||
|
// to follow the specified Format.
|
||||||
|
func NewParser(r io.Reader, format Format) *Parser {
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
|
||||||
|
// in addition to the reference delimiter we specified in the --format,
|
||||||
|
// `git for-each-ref` will always add a newline after every reference.
|
||||||
|
refDelim := make([]byte, 0, len(format.refDelim)+1)
|
||||||
|
refDelim = append(refDelim, format.refDelim...)
|
||||||
|
refDelim = append(refDelim, '\n')
|
||||||
|
|
||||||
|
// Split input into delimiter-separated "reference blocks".
|
||||||
|
scanner.Split(
|
||||||
|
func(data []byte, atEOF bool) (int, []byte, error) {
|
||||||
|
// Scan until delimiter, marking end of reference.
|
||||||
|
delimIdx := bytes.Index(data, refDelim)
|
||||||
|
if delimIdx >= 0 {
|
||||||
|
token := data[:delimIdx]
|
||||||
|
advance := delimIdx + len(refDelim)
|
||||||
|
return advance, token, nil
|
||||||
|
}
|
||||||
|
// If we're at EOF, we have a final, non-terminated reference. Return it.
|
||||||
|
if atEOF {
|
||||||
|
return len(data), data, nil
|
||||||
|
}
|
||||||
|
// Not yet a full field. Request more data.
|
||||||
|
return 0, nil, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return &Parser{
|
||||||
|
scanner: scanner,
|
||||||
|
format: format,
|
||||||
|
err: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next reference as a collection of key-value pairs. nil
|
||||||
|
// denotes EOF but is also returned on errors. The Err method should always be
|
||||||
|
// consulted after Next returning nil.
|
||||||
|
//
|
||||||
|
// It could, for example return something like:
|
||||||
|
//
|
||||||
|
// { "objecttype": "tag", "refname:short": "v1.16.4", "object": "f460b7543ed500e49c133c2cd85c8c55ee9dbe27" }
|
||||||
|
func (p *Parser) Next() map[string]string {
|
||||||
|
if !p.scanner.Scan() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fields, err := p.parseRef(p.scanner.Text())
|
||||||
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
p.err = err
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the latest encountered parsing error.
|
||||||
|
func (p *Parser) Err() error {
|
||||||
|
return p.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseRef parses out all key-value pairs from a single reference block, such as
|
||||||
|
//
|
||||||
|
// "type tag\0ref:short v1.16.4\0object f460b7543ed500e49c133c2cd85c8c55ee9dbe27"
|
||||||
|
func (p *Parser) parseRef(refBlock string) (map[string]string, error) {
|
||||||
|
if refBlock == "" {
|
||||||
|
// must be at EOF
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldValues := make(map[string]string)
|
||||||
|
|
||||||
|
fields := strings.Split(refBlock, p.format.fieldDelimStr)
|
||||||
|
if len(fields) != len(p.format.fieldNames) {
|
||||||
|
return nil, fmt.Errorf("unexpected number of reference fields: wanted %d, was %d",
|
||||||
|
len(fields), len(p.format.fieldNames))
|
||||||
|
}
|
||||||
|
for i, field := range fields {
|
||||||
|
field = strings.TrimSpace(field)
|
||||||
|
|
||||||
|
var fieldKey string
|
||||||
|
var fieldVal string
|
||||||
|
firstSpace := strings.Index(field, " ")
|
||||||
|
if firstSpace > 0 {
|
||||||
|
fieldKey = field[:firstSpace]
|
||||||
|
fieldVal = field[firstSpace+1:]
|
||||||
|
} else {
|
||||||
|
// could be the case if the requested field had no value
|
||||||
|
fieldKey = field
|
||||||
|
}
|
||||||
|
|
||||||
|
// enforce the format order of fields
|
||||||
|
if p.format.fieldNames[i] != fieldKey {
|
||||||
|
return nil, fmt.Errorf("unexpected field name at position %d: wanted: '%s', was: '%s'",
|
||||||
|
i, p.format.fieldNames[i], fieldKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldValues[fieldKey] = fieldVal
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldValues, nil
|
||||||
|
}
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
const (
|
const (
|
||||||
fmtEOL = "%n"
|
fmtEOL = "%n"
|
||||||
@ -34,5 +34,5 @@ const (
|
|||||||
fmtCommitterUnix = "%ct" // Unix timestamp
|
fmtCommitterUnix = "%ct" // Unix timestamp
|
||||||
|
|
||||||
fmtSubject = "%s"
|
fmtSubject = "%s"
|
||||||
fmtBody = "%b"
|
fmtBody = "%B"
|
||||||
)
|
)
|
@ -12,36 +12,39 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
|
"github.com/harness/gitness/git/command"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a Adapter) InfoRefs(
|
func (g *Git) InfoRefs(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
service string,
|
service string,
|
||||||
w io.Writer,
|
w io.Writer,
|
||||||
env ...string,
|
env ...string,
|
||||||
) error {
|
) error {
|
||||||
cmd := &bytes.Buffer{}
|
stdout := &bytes.Buffer{}
|
||||||
if err := git.NewCommand(ctx, service, "--stateless-rpc", "--advertise-refs", ".").
|
cmd := command.New(service,
|
||||||
Run(&git.RunOpts{
|
command.WithFlag("--stateless-rpc"),
|
||||||
Env: env,
|
command.WithFlag("--advertise-refs"),
|
||||||
Dir: repoPath,
|
command.WithArg("."),
|
||||||
Stdout: cmd,
|
)
|
||||||
}); err != nil {
|
if err := cmd.Run(ctx,
|
||||||
|
command.WithDir(repoPath),
|
||||||
|
command.WithStdout(stdout),
|
||||||
|
command.WithEnvs(env...),
|
||||||
|
); err != nil {
|
||||||
return errors.Internal(err, "InfoRefs service %s failed", service)
|
return errors.Internal(err, "InfoRefs service %s failed", service)
|
||||||
}
|
}
|
||||||
if _, err := w.Write(packetWrite("# service=git-" + service + "\n")); err != nil {
|
if _, err := w.Write(packetWrite("# service=git-" + service + "\n")); err != nil {
|
||||||
@ -52,13 +55,13 @@ func (a Adapter) InfoRefs(
|
|||||||
return errors.Internal(err, "failed to flush data in InfoRefs %s service", service)
|
return errors.Internal(err, "failed to flush data in InfoRefs %s service", service)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := io.Copy(w, cmd); err != nil {
|
if _, err := io.Copy(w, stdout); err != nil {
|
||||||
return errors.Internal(err, "streaming InfoRefs %s service failed", service)
|
return errors.Internal(err, "streaming InfoRefs %s service failed", service)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Adapter) ServicePack(
|
func (g *Git) ServicePack(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
service string,
|
service string,
|
||||||
@ -66,24 +69,19 @@ func (a Adapter) ServicePack(
|
|||||||
stdout io.Writer,
|
stdout io.Writer,
|
||||||
env ...string,
|
env ...string,
|
||||||
) error {
|
) error {
|
||||||
// set this for allow pre-receive and post-receive execute
|
cmd := command.New(service,
|
||||||
env = append(env, "SSH_ORIGINAL_COMMAND="+service)
|
command.WithFlag("--stateless-rpc"),
|
||||||
|
command.WithArg(repoPath),
|
||||||
var (
|
command.WithEnv("SSH_ORIGINAL_COMMAND", service),
|
||||||
stderr bytes.Buffer
|
)
|
||||||
|
err := cmd.Run(ctx,
|
||||||
|
command.WithDir(repoPath),
|
||||||
|
command.WithStdout(stdout),
|
||||||
|
command.WithStdin(stdin),
|
||||||
|
command.WithEnvs(env...),
|
||||||
)
|
)
|
||||||
cmd := git.NewCommand(ctx, service, "--stateless-rpc", repoPath)
|
|
||||||
cmd.SetDescription(fmt.Sprintf("%s %s %s [repo_path: %s]", git.GitExecutable, service, "--stateless-rpc", repoPath))
|
|
||||||
err := cmd.Run(&git.RunOpts{
|
|
||||||
Dir: repoPath,
|
|
||||||
Env: env,
|
|
||||||
Stdout: stdout,
|
|
||||||
Stdin: stdin,
|
|
||||||
Stderr: &stderr,
|
|
||||||
UseContextTimeout: true,
|
|
||||||
})
|
|
||||||
if err != nil && err.Error() != "signal: killed" {
|
if err != nil && err.Error() != "signal: killed" {
|
||||||
log.Ctx(ctx).Err(err).Msgf("Fail to serve RPC(%s) in %s: %v - %s", service, repoPath, err, stderr.String())
|
log.Ctx(ctx).Err(err).Msgf("Fail to serve RPC(%s) in %s: %v", service, repoPath, err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -25,15 +25,15 @@ import (
|
|||||||
|
|
||||||
"github.com/harness/gitness/cache"
|
"github.com/harness/gitness/cache"
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/sha"
|
||||||
|
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewInMemoryLastCommitCache(
|
func NewInMemoryLastCommitCache(
|
||||||
cacheDuration time.Duration,
|
cacheDuration time.Duration,
|
||||||
) cache.Cache[CommitEntryKey, *types.Commit] {
|
) cache.Cache[CommitEntryKey, *Commit] {
|
||||||
return cache.New[CommitEntryKey, *types.Commit](
|
return cache.New[CommitEntryKey, *Commit](
|
||||||
commitEntryGetter{},
|
commitEntryGetter{},
|
||||||
cacheDuration)
|
cacheDuration)
|
||||||
}
|
}
|
||||||
@ -41,12 +41,12 @@ func NewInMemoryLastCommitCache(
|
|||||||
func NewRedisLastCommitCache(
|
func NewRedisLastCommitCache(
|
||||||
redisClient redis.UniversalClient,
|
redisClient redis.UniversalClient,
|
||||||
cacheDuration time.Duration,
|
cacheDuration time.Duration,
|
||||||
) (cache.Cache[CommitEntryKey, *types.Commit], error) {
|
) (cache.Cache[CommitEntryKey, *Commit], error) {
|
||||||
if redisClient == nil {
|
if redisClient == nil {
|
||||||
return nil, errors.New("unable to create redis based LastCommitCache as redis client is nil")
|
return nil, errors.New("unable to create redis based LastCommitCache as redis client is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
return cache.NewRedis[CommitEntryKey, *types.Commit](
|
return cache.NewRedis[CommitEntryKey, *Commit](
|
||||||
redisClient,
|
redisClient,
|
||||||
commitEntryGetter{},
|
commitEntryGetter{},
|
||||||
func(key CommitEntryKey) string {
|
func(key CommitEntryKey) string {
|
||||||
@ -58,8 +58,8 @@ func NewRedisLastCommitCache(
|
|||||||
cacheDuration), nil
|
cacheDuration), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NoLastCommitCache() cache.Cache[CommitEntryKey, *types.Commit] {
|
func NoLastCommitCache() cache.Cache[CommitEntryKey, *Commit] {
|
||||||
return cache.NewNoCache[CommitEntryKey, *types.Commit](commitEntryGetter{})
|
return cache.NewNoCache[CommitEntryKey, *Commit](commitEntryGetter{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommitEntryKey string
|
type CommitEntryKey string
|
||||||
@ -68,10 +68,10 @@ const separatorZero = "\x00"
|
|||||||
|
|
||||||
func makeCommitEntryKey(
|
func makeCommitEntryKey(
|
||||||
repoPath string,
|
repoPath string,
|
||||||
commitSHA string,
|
commitSHA sha.SHA,
|
||||||
path string,
|
path string,
|
||||||
) CommitEntryKey {
|
) CommitEntryKey {
|
||||||
return CommitEntryKey(repoPath + separatorZero + commitSHA + separatorZero + path)
|
return CommitEntryKey(repoPath + separatorZero + commitSHA.String() + separatorZero + path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c CommitEntryKey) Split() (
|
func (c CommitEntryKey) Split() (
|
||||||
@ -93,14 +93,14 @@ func (c CommitEntryKey) Split() (
|
|||||||
|
|
||||||
type commitValueCodec struct{}
|
type commitValueCodec struct{}
|
||||||
|
|
||||||
func (c commitValueCodec) Encode(v *types.Commit) string {
|
func (c commitValueCodec) Encode(v *Commit) string {
|
||||||
buffer := &strings.Builder{}
|
buffer := &strings.Builder{}
|
||||||
_ = gob.NewEncoder(buffer).Encode(v)
|
_ = gob.NewEncoder(buffer).Encode(v)
|
||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c commitValueCodec) Decode(s string) (*types.Commit, error) {
|
func (c commitValueCodec) Decode(s string) (*Commit, error) {
|
||||||
commit := &types.Commit{}
|
commit := &Commit{}
|
||||||
if err := gob.NewDecoder(strings.NewReader(s)).Decode(commit); err != nil {
|
if err := gob.NewDecoder(strings.NewReader(s)).Decode(commit); err != nil {
|
||||||
return nil, fmt.Errorf("failed to unpack commit entry value: %w", err)
|
return nil, fmt.Errorf("failed to unpack commit entry value: %w", err)
|
||||||
}
|
}
|
||||||
@ -114,12 +114,12 @@ type commitEntryGetter struct{}
|
|||||||
func (c commitEntryGetter) Find(
|
func (c commitEntryGetter) Find(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
key CommitEntryKey,
|
key CommitEntryKey,
|
||||||
) (*types.Commit, error) {
|
) (*Commit, error) {
|
||||||
repoPath, commitSHA, path := key.Split()
|
repoPath, commitSHA, path := key.Split()
|
||||||
|
|
||||||
if path == "" {
|
if path == "" {
|
||||||
path = "."
|
path = "."
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetCommit(ctx, repoPath, commitSHA, path)
|
return getCommit(ctx, repoPath, commitSHA, path)
|
||||||
}
|
}
|
51
git/api/mapping.go
Normal file
51
git/api/mapping.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2023 Harness, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
func mapRawRef(
|
||||||
|
raw map[string]string,
|
||||||
|
) (map[GitReferenceField]string, error) {
|
||||||
|
res := make(map[GitReferenceField]string, len(raw))
|
||||||
|
for k, v := range raw {
|
||||||
|
gitRefField, err := ParseGitReferenceField(k)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res[gitRefField] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapToReferenceSortingArgument(
|
||||||
|
s GitReferenceField,
|
||||||
|
o SortOrder,
|
||||||
|
) string {
|
||||||
|
sortBy := string(GitReferenceFieldRefName)
|
||||||
|
desc := o == SortOrderDesc
|
||||||
|
|
||||||
|
if s == GitReferenceFieldCreatorDate {
|
||||||
|
sortBy = string(GitReferenceFieldCreatorDate)
|
||||||
|
if o == SortOrderDefault {
|
||||||
|
desc = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if desc {
|
||||||
|
return "-" + sortBy
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortBy
|
||||||
|
}
|
@ -12,39 +12,40 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/types"
|
|
||||||
|
|
||||||
gitea "code.gitea.io/gitea/modules/git"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type FileContent struct {
|
||||||
|
Path string
|
||||||
|
Content []byte
|
||||||
|
}
|
||||||
|
|
||||||
//nolint:gocognit
|
//nolint:gocognit
|
||||||
func (a Adapter) MatchFiles(
|
func (g *Git) MatchFiles(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
rev string,
|
rev string,
|
||||||
treePath string,
|
treePath string,
|
||||||
pattern string,
|
pattern string,
|
||||||
maxSize int,
|
maxSize int,
|
||||||
) ([]types.FileContent, error) {
|
) ([]FileContent, error) {
|
||||||
nodes, err := lsDirectory(ctx, repoPath, rev, treePath)
|
nodes, err := lsDirectory(ctx, repoPath, rev, treePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to list files in match files: %w", err)
|
return nil, fmt.Errorf("failed to list files in match files: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
catFileWriter, catFileReader, catFileStop := gitea.CatFileBatch(ctx, repoPath)
|
catFileWriter, catFileReader, catFileStop := CatFileBatch(ctx, repoPath)
|
||||||
defer catFileStop()
|
defer catFileStop()
|
||||||
|
|
||||||
var files []types.FileContent
|
var files []FileContent
|
||||||
for i := range nodes {
|
for i := range nodes {
|
||||||
if nodes[i].NodeType != types.TreeNodeTypeBlob {
|
if nodes[i].NodeType != TreeNodeTypeBlob {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,19 +58,19 @@ func (a Adapter) MatchFiles(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = catFileWriter.Write([]byte(nodes[i].Sha + "\n"))
|
_, err = catFileWriter.Write([]byte(nodes[i].SHA.String() + "\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to ask for file content from cat file batch: %w", err)
|
return nil, fmt.Errorf("failed to ask for file content from cat file batch: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, size, err := gitea.ReadBatchLine(catFileReader)
|
output, err := ReadBatchHeaderLine(catFileReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read cat-file batch header: %w", err)
|
return nil, fmt.Errorf("failed to read cat-file batch header: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reader := io.LimitReader(catFileReader, size+1) // plus eol
|
reader := io.LimitReader(catFileReader, output.Size+1) // plus eol
|
||||||
|
|
||||||
if size > int64(maxSize) {
|
if output.Size > int64(maxSize) {
|
||||||
_, err = io.Copy(io.Discard, reader)
|
_, err = io.Copy(io.Discard, reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to discard a large file: %w", err)
|
return nil, fmt.Errorf("failed to discard a large file: %w", err)
|
||||||
@ -89,7 +90,7 @@ func (a Adapter) MatchFiles(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
files = append(files, types.FileContent{
|
files = append(files, FileContent{
|
||||||
Path: nodes[i].Path,
|
Path: nodes[i].Path,
|
||||||
Content: data,
|
Content: data,
|
||||||
})
|
})
|
104
git/api/merge.go
Normal file
104
git/api/merge.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// Copyright 2023 Harness, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/git/command"
|
||||||
|
"github.com/harness/gitness/git/sha"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RemotePrefix is the base directory of the remotes information of git.
|
||||||
|
RemotePrefix = "refs/remotes/"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetMergeBase checks and returns merge base of two branches and the reference used as base.
|
||||||
|
func (g *Git) GetMergeBase(
|
||||||
|
ctx context.Context,
|
||||||
|
repoPath string,
|
||||||
|
remote string,
|
||||||
|
base string,
|
||||||
|
head string,
|
||||||
|
) (sha.SHA, string, error) {
|
||||||
|
if repoPath == "" {
|
||||||
|
return sha.None, "", ErrRepositoryPathEmpty
|
||||||
|
}
|
||||||
|
if remote == "" {
|
||||||
|
remote = "origin"
|
||||||
|
}
|
||||||
|
|
||||||
|
if remote != "origin" {
|
||||||
|
tmpBaseName := RemotePrefix + remote + "/tmp_" + base
|
||||||
|
// Fetch commit into a temporary branch in order to be able to handle commits and tags
|
||||||
|
cmd := command.New("fetch",
|
||||||
|
command.WithFlag("--no-tags"),
|
||||||
|
command.WithArg(remote),
|
||||||
|
command.WithPostSepArg(base+":"+tmpBaseName),
|
||||||
|
)
|
||||||
|
err := cmd.Run(ctx, command.WithDir(repoPath))
|
||||||
|
if err == nil {
|
||||||
|
base = tmpBaseName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout := &bytes.Buffer{}
|
||||||
|
cmd := command.New("merge-base",
|
||||||
|
command.WithArg(base, head),
|
||||||
|
)
|
||||||
|
err := cmd.Run(ctx,
|
||||||
|
command.WithDir(repoPath),
|
||||||
|
command.WithStdout(stdout),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return sha.None, "", processGitErrorf(err, "failed to get merge-base [%s, %s]", base, head)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := sha.New(stdout.String())
|
||||||
|
if err != nil {
|
||||||
|
return sha.None, "", err
|
||||||
|
}
|
||||||
|
return result, base, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAncestor returns if the provided commit SHA is ancestor of the other commit SHA.
|
||||||
|
func (g *Git) IsAncestor(
|
||||||
|
ctx context.Context,
|
||||||
|
repoPath string,
|
||||||
|
ancestorCommitSHA, descendantCommitSHA sha.SHA,
|
||||||
|
) (bool, error) {
|
||||||
|
if repoPath == "" {
|
||||||
|
return false, ErrRepositoryPathEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := command.New("merge-base",
|
||||||
|
command.WithFlag("--is-ancestor"),
|
||||||
|
command.WithArg(ancestorCommitSHA.String(), descendantCommitSHA.String()),
|
||||||
|
)
|
||||||
|
|
||||||
|
err := cmd.Run(ctx, command.WithDir(repoPath))
|
||||||
|
if err != nil {
|
||||||
|
cmdErr := command.AsError(err)
|
||||||
|
if cmdErr != nil && cmdErr.IsExitCode(1) && len(cmdErr.StdErr) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, processGitErrorf(err, "failed to check commit ancestry [%s, %s]",
|
||||||
|
ancestorCommitSHA, descendantCommitSHA)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
74
git/api/object.go
Normal file
74
git/api/object.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2023 Harness, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/git/command"
|
||||||
|
"github.com/harness/gitness/git/sha"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GitObjectType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
GitObjectTypeCommit GitObjectType = "commit"
|
||||||
|
GitObjectTypeTree GitObjectType = "tree"
|
||||||
|
GitObjectTypeBlob GitObjectType = "blob"
|
||||||
|
GitObjectTypeTag GitObjectType = "tag"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseGitObjectType(t string) (GitObjectType, error) {
|
||||||
|
switch t {
|
||||||
|
case string(GitObjectTypeCommit):
|
||||||
|
return GitObjectTypeCommit, nil
|
||||||
|
case string(GitObjectTypeBlob):
|
||||||
|
return GitObjectTypeBlob, nil
|
||||||
|
case string(GitObjectTypeTree):
|
||||||
|
return GitObjectTypeTree, nil
|
||||||
|
case string(GitObjectTypeTag):
|
||||||
|
return GitObjectTypeTag, nil
|
||||||
|
default:
|
||||||
|
return GitObjectTypeBlob, fmt.Errorf("unknown git object type '%s'", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SortOrder int
|
||||||
|
|
||||||
|
const (
|
||||||
|
SortOrderDefault SortOrder = iota
|
||||||
|
SortOrderAsc
|
||||||
|
SortOrderDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func (g *Git) HashObject(ctx context.Context, repoPath string, reader io.Reader) (sha.SHA, error) {
|
||||||
|
cmd := command.New("hash-object",
|
||||||
|
command.WithFlag("-w"),
|
||||||
|
command.WithFlag("--stdin"),
|
||||||
|
)
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
err := cmd.Run(ctx,
|
||||||
|
command.WithDir(repoPath),
|
||||||
|
command.WithStdin(reader),
|
||||||
|
command.WithStdout(stdout),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return sha.None, fmt.Errorf("failed to hash object: %w", err)
|
||||||
|
}
|
||||||
|
return sha.New(stdout.String())
|
||||||
|
}
|
@ -12,35 +12,38 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type PathDetails struct {
|
||||||
|
Path string
|
||||||
|
LastCommit *Commit
|
||||||
|
}
|
||||||
|
|
||||||
// PathsDetails returns additional details about provided the paths.
|
// PathsDetails returns additional details about provided the paths.
|
||||||
func (a Adapter) PathsDetails(ctx context.Context,
|
func (g *Git) PathsDetails(ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
rev string,
|
rev string,
|
||||||
paths []string,
|
paths []string,
|
||||||
) ([]types.PathDetails, error) {
|
) ([]PathDetails, error) {
|
||||||
// resolve the git revision to the commit SHA - we need the commit SHA for the last commit hash entry key.
|
// resolve the git revision to the commit SHA - we need the commit SHA for the last commit hash entry key.
|
||||||
commitSHA, err := a.ResolveRev(ctx, repoPath, rev)
|
commitSHA, err := g.ResolveRev(ctx, repoPath, rev)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get path details: %w", err)
|
return nil, fmt.Errorf("failed to get path details: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
results := make([]types.PathDetails, len(paths))
|
results := make([]PathDetails, len(paths))
|
||||||
|
|
||||||
for i, path := range paths {
|
for i, path := range paths {
|
||||||
results[i].Path = path
|
results[i].Path = path
|
||||||
|
|
||||||
path = cleanTreePath(path) // use cleaned-up path for calculations to avoid not-founds.
|
path = cleanTreePath(path) // use cleaned-up path for calculations to avoid not-founds.
|
||||||
|
|
||||||
commitEntry, err := a.lastCommitCache.Get(ctx, makeCommitEntryKey(repoPath, commitSHA, path))
|
commitEntry, err := g.lastCommitCache.Get(ctx, makeCommitEntryKey(repoPath, commitSHA, path))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to find last commit for path %s: %w", path, err)
|
return nil, fmt.Errorf("failed to find last commit for path %s: %w", path, err)
|
||||||
}
|
}
|
@ -12,38 +12,112 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/errors"
|
||||||
|
"github.com/harness/gitness/git/api/foreachref"
|
||||||
|
"github.com/harness/gitness/git/command"
|
||||||
"github.com/harness/gitness/git/hook"
|
"github.com/harness/gitness/git/hook"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/sha"
|
||||||
|
|
||||||
gitea "code.gitea.io/gitea/modules/git"
|
|
||||||
gitearef "code.gitea.io/gitea/modules/git/foreachref"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GitReferenceField represents the different fields available When listing references.
|
||||||
|
// For the full list, see https://git-scm.com/docs/git-for-each-ref#_field_names
|
||||||
|
type GitReferenceField string
|
||||||
|
|
||||||
|
const (
|
||||||
|
GitReferenceFieldRefName GitReferenceField = "refname"
|
||||||
|
GitReferenceFieldObjectType GitReferenceField = "objecttype"
|
||||||
|
GitReferenceFieldObjectName GitReferenceField = "objectname"
|
||||||
|
GitReferenceFieldCreatorDate GitReferenceField = "creatordate"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseGitReferenceField(f string) (GitReferenceField, error) {
|
||||||
|
switch f {
|
||||||
|
case string(GitReferenceFieldCreatorDate):
|
||||||
|
return GitReferenceFieldCreatorDate, nil
|
||||||
|
case string(GitReferenceFieldRefName):
|
||||||
|
return GitReferenceFieldRefName, nil
|
||||||
|
case string(GitReferenceFieldObjectName):
|
||||||
|
return GitReferenceFieldObjectName, nil
|
||||||
|
case string(GitReferenceFieldObjectType):
|
||||||
|
return GitReferenceFieldObjectType, nil
|
||||||
|
default:
|
||||||
|
return GitReferenceFieldRefName, fmt.Errorf("unknown git reference field '%s'", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type WalkInstruction int
|
||||||
|
|
||||||
|
const (
|
||||||
|
WalkInstructionStop WalkInstruction = iota
|
||||||
|
WalkInstructionHandle
|
||||||
|
WalkInstructionSkip
|
||||||
|
)
|
||||||
|
|
||||||
|
type WalkReferencesEntry map[GitReferenceField]string
|
||||||
|
|
||||||
|
// TODO: can be generic (so other walk methods can use the same)
|
||||||
|
type WalkReferencesInstructor func(WalkReferencesEntry) (WalkInstruction, error)
|
||||||
|
|
||||||
|
// TODO: can be generic (so other walk methods can use the same)
|
||||||
|
type WalkReferencesHandler func(WalkReferencesEntry) error
|
||||||
|
|
||||||
|
type WalkReferencesOptions struct {
|
||||||
|
// Patterns are the patterns used to pre-filter the references of the repo.
|
||||||
|
// OPTIONAL. By default all references are walked.
|
||||||
|
Patterns []string
|
||||||
|
|
||||||
|
// Fields indicates the fields that are passed to the instructor & handler
|
||||||
|
// OPTIONAL. Default fields are:
|
||||||
|
// - GitReferenceFieldRefName
|
||||||
|
// - GitReferenceFieldObjectName
|
||||||
|
Fields []GitReferenceField
|
||||||
|
|
||||||
|
// Instructor indicates on how to handle the reference.
|
||||||
|
// OPTIONAL. By default all references are handled.
|
||||||
|
// NOTE: once walkInstructionStop is returned, the walking stops.
|
||||||
|
Instructor WalkReferencesInstructor
|
||||||
|
|
||||||
|
// Sort indicates the field by which the references should be sorted.
|
||||||
|
// OPTIONAL. By default GitReferenceFieldRefName is used.
|
||||||
|
Sort GitReferenceField
|
||||||
|
|
||||||
|
// Order indicates the Order (asc or desc) of the sorted output
|
||||||
|
Order SortOrder
|
||||||
|
|
||||||
|
// MaxWalkDistance is the maximum number of nodes that are iterated over before the walking stops.
|
||||||
|
// OPTIONAL. A value of <= 0 will walk all references.
|
||||||
|
// WARNING: Skipped elements count towards the walking distance
|
||||||
|
MaxWalkDistance int32
|
||||||
|
}
|
||||||
|
|
||||||
func DefaultInstructor(
|
func DefaultInstructor(
|
||||||
_ types.WalkReferencesEntry,
|
_ WalkReferencesEntry,
|
||||||
) (types.WalkInstruction, error) {
|
) (WalkInstruction, error) {
|
||||||
return types.WalkInstructionHandle, nil
|
return WalkInstructionHandle, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WalkReferences uses the provided options to filter the available references of the repo,
|
// WalkReferences uses the provided options to filter the available references of the repo,
|
||||||
// and calls the handle function for every matching node.
|
// and calls the handle function for every matching node.
|
||||||
// The instructor & handler are called with a map that contains the matching value for every field provided in fields.
|
// The instructor & handler are called with a map that contains the matching value for every field provided in fields.
|
||||||
// TODO: walkGiteaReferences related code should be moved to separate file.
|
// TODO: walkReferences related code should be moved to separate file.
|
||||||
func (a Adapter) WalkReferences(
|
func (g *Git) WalkReferences(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
handler types.WalkReferencesHandler,
|
handler WalkReferencesHandler,
|
||||||
opts *types.WalkReferencesOptions,
|
opts *WalkReferencesOptions,
|
||||||
) error {
|
) error {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return ErrRepositoryPathEmpty
|
return ErrRepositoryPathEmpty
|
||||||
@ -53,7 +127,7 @@ func (a Adapter) WalkReferences(
|
|||||||
opts.Instructor = DefaultInstructor
|
opts.Instructor = DefaultInstructor
|
||||||
}
|
}
|
||||||
if len(opts.Fields) == 0 {
|
if len(opts.Fields) == 0 {
|
||||||
opts.Fields = []types.GitReferenceField{types.GitReferenceFieldRefName, types.GitReferenceFieldObjectName}
|
opts.Fields = []GitReferenceField{GitReferenceFieldRefName, GitReferenceFieldObjectName}
|
||||||
}
|
}
|
||||||
if opts.MaxWalkDistance <= 0 {
|
if opts.MaxWalkDistance <= 0 {
|
||||||
opts.MaxWalkDistance = math.MaxInt32
|
opts.MaxWalkDistance = math.MaxInt32
|
||||||
@ -62,40 +136,35 @@ func (a Adapter) WalkReferences(
|
|||||||
opts.Patterns = []string{}
|
opts.Patterns = []string{}
|
||||||
}
|
}
|
||||||
if string(opts.Sort) == "" {
|
if string(opts.Sort) == "" {
|
||||||
opts.Sort = types.GitReferenceFieldRefName
|
opts.Sort = GitReferenceFieldRefName
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare for-each-ref input
|
// prepare for-each-ref input
|
||||||
sortArg := mapToGiteaReferenceSortingArgument(opts.Sort, opts.Order)
|
sortArg := mapToReferenceSortingArgument(opts.Sort, opts.Order)
|
||||||
rawFields := make([]string, len(opts.Fields))
|
rawFields := make([]string, len(opts.Fields))
|
||||||
for i := range opts.Fields {
|
for i := range opts.Fields {
|
||||||
rawFields[i] = string(opts.Fields[i])
|
rawFields[i] = string(opts.Fields[i])
|
||||||
}
|
}
|
||||||
giteaFormat := gitearef.NewFormat(rawFields...)
|
format := foreachref.NewFormat(rawFields...)
|
||||||
|
|
||||||
// initializer pipeline for output processing
|
// initializer pipeline for output processing
|
||||||
pipeOut, pipeIn := io.Pipe()
|
pipeOut, pipeIn := io.Pipe()
|
||||||
defer pipeOut.Close()
|
defer pipeOut.Close()
|
||||||
defer pipeIn.Close()
|
|
||||||
stderr := strings.Builder{}
|
|
||||||
rc := &gitea.RunOpts{Dir: repoPath, Stdout: pipeIn, Stderr: &stderr}
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// create array for args as patterns have to be passed as separate args.
|
cmd := command.New("for-each-ref",
|
||||||
args := []string{
|
command.WithFlag("--format", format.Flag()),
|
||||||
"for-each-ref",
|
command.WithFlag("--sort", sortArg),
|
||||||
"--format",
|
command.WithFlag("--count", strconv.Itoa(int(opts.MaxWalkDistance))),
|
||||||
giteaFormat.Flag(),
|
command.WithFlag("--ignore-case"),
|
||||||
"--sort",
|
)
|
||||||
sortArg,
|
cmd.Add(command.WithArg(opts.Patterns...))
|
||||||
"--count",
|
err := cmd.Run(ctx,
|
||||||
fmt.Sprint(opts.MaxWalkDistance),
|
command.WithDir(repoPath),
|
||||||
"--ignore-case",
|
command.WithStdout(pipeIn),
|
||||||
}
|
)
|
||||||
args = append(args, opts.Patterns...)
|
|
||||||
err := gitea.NewCommand(ctx, args...).Run(rc)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = pipeIn.CloseWithError(gitea.ConcatenateError(err, stderr.String()))
|
_ = pipeIn.CloseWithError(err)
|
||||||
} else {
|
} else {
|
||||||
_ = pipeIn.Close()
|
_ = pipeIn.Close()
|
||||||
}
|
}
|
||||||
@ -103,14 +172,14 @@ func (a Adapter) WalkReferences(
|
|||||||
|
|
||||||
// TODO: return error from git command!!!!
|
// TODO: return error from git command!!!!
|
||||||
|
|
||||||
parser := giteaFormat.Parser(pipeOut)
|
parser := format.Parser(pipeOut)
|
||||||
return walkGiteaReferenceParser(parser, handler, opts)
|
return walkReferenceParser(parser, handler, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func walkGiteaReferenceParser(
|
func walkReferenceParser(
|
||||||
parser *gitearef.Parser,
|
parser *foreachref.Parser,
|
||||||
handler types.WalkReferencesHandler,
|
handler WalkReferencesHandler,
|
||||||
opts *types.WalkReferencesOptions,
|
opts *WalkReferencesOptions,
|
||||||
) error {
|
) error {
|
||||||
for i := int32(0); i < opts.MaxWalkDistance; i++ {
|
for i := int32(0); i < opts.MaxWalkDistance; i++ {
|
||||||
// parse next line - nil if end of output reached or an error occurred.
|
// parse next line - nil if end of output reached or an error occurred.
|
||||||
@ -120,7 +189,7 @@ func walkGiteaReferenceParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// convert to correct map.
|
// convert to correct map.
|
||||||
ref, err := mapGiteaRawRef(rawRef)
|
ref, err := mapRawRef(rawRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -131,10 +200,10 @@ func walkGiteaReferenceParser(
|
|||||||
return fmt.Errorf("error getting instruction: %w", err)
|
return fmt.Errorf("error getting instruction: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if instruction == types.WalkInstructionSkip {
|
if instruction == WalkInstructionSkip {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if instruction == types.WalkInstructionStop {
|
if instruction == WalkInstructionStop {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +215,7 @@ func walkGiteaReferenceParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := parser.Err(); err != nil {
|
if err := parser.Err(); err != nil {
|
||||||
return processGiteaErrorf(err, "failed to parse reference walk output")
|
return processGitErrorf(err, "failed to parse reference walk output")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -155,60 +224,63 @@ func walkGiteaReferenceParser(
|
|||||||
// GetRef get's the target of a reference
|
// GetRef get's the target of a reference
|
||||||
// IMPORTANT provide full reference name to limit risk of collisions across reference types
|
// IMPORTANT provide full reference name to limit risk of collisions across reference types
|
||||||
// (e.g `refs/heads/main` instead of `main`).
|
// (e.g `refs/heads/main` instead of `main`).
|
||||||
func (a Adapter) GetRef(
|
func (g *Git) GetRef(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
ref string,
|
ref string,
|
||||||
) (string, error) {
|
) (sha.SHA, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return "", ErrRepositoryPathEmpty
|
return sha.None, ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
cmd := gitea.NewCommand(ctx, "show-ref", "--verify", "-s", "--", ref)
|
cmd := command.New("show-ref",
|
||||||
stdout, _, err := cmd.RunStdString(&gitea.RunOpts{
|
command.WithFlag("--verify"),
|
||||||
Dir: repoPath,
|
command.WithFlag("-s"),
|
||||||
})
|
command.WithArg(ref),
|
||||||
|
)
|
||||||
|
output := &bytes.Buffer{}
|
||||||
|
err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(output))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.IsExitCode(128) && strings.Contains(err.Stderr(), "not a valid ref") {
|
if command.AsError(err).IsExitCode(128) && strings.Contains(err.Error(), "not a valid ref") {
|
||||||
return "", types.ErrNotFound("reference %q not found", ref)
|
return sha.None, errors.NotFound("reference %q not found", ref)
|
||||||
}
|
}
|
||||||
return "", err
|
return sha.None, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.TrimSpace(stdout), nil
|
return sha.New(output.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRef allows to update / create / delete references
|
// UpdateRef allows to update / create / delete references
|
||||||
// IMPORTANT provide full reference name to limit risk of collisions across reference types
|
// IMPORTANT provide full reference name to limit risk of collisions across reference types
|
||||||
// (e.g `refs/heads/main` instead of `main`).
|
// (e.g `refs/heads/main` instead of `main`).
|
||||||
func (a Adapter) UpdateRef(
|
func (g *Git) UpdateRef(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
envVars map[string]string,
|
envVars map[string]string,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
ref string,
|
ref string,
|
||||||
oldValue string,
|
oldValue sha.SHA,
|
||||||
newValue string,
|
newValue sha.SHA,
|
||||||
) error {
|
) error {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return ErrRepositoryPathEmpty
|
return ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't break existing interface - user calls with empty value to delete the ref.
|
// don't break existing interface - user calls with empty value to delete the ref.
|
||||||
if newValue == "" {
|
if newValue.IsEmpty() {
|
||||||
newValue = types.NilSHA
|
newValue = sha.Nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no old value was provided, use current value (as required for hooks)
|
// if no old value was provided, use current value (as required for hooks)
|
||||||
// TODO: technically a delete could fail if someone updated the ref in the meanwhile.
|
// TODO: technically a delete could fail if someone updated the ref in the meanwhile.
|
||||||
//nolint:gocritic,nestif
|
//nolint:gocritic,nestif
|
||||||
if oldValue == "" {
|
if oldValue.IsEmpty() {
|
||||||
val, err := a.GetRef(ctx, repoPath, ref)
|
val, err := g.GetRef(ctx, repoPath, ref)
|
||||||
if types.IsNotFoundError(err) {
|
if errors.IsNotFound(err) {
|
||||||
// fail in case someone tries to delete a reference that doesn't exist.
|
// fail in case someone tries to delete a reference that doesn't exist.
|
||||||
if newValue == types.NilSHA {
|
if newValue.IsNil() {
|
||||||
return types.ErrNotFound("reference %q not found", ref)
|
return errors.NotFound("reference %q not found", ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
oldValue = types.NilSHA
|
oldValue = sha.Nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("failed to get current value of reference: %w", err)
|
return fmt.Errorf("failed to get current value of reference: %w", err)
|
||||||
} else {
|
} else {
|
||||||
@ -216,7 +288,7 @@ func (a Adapter) UpdateRef(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := a.updateRefWithHooks(
|
err := g.updateRefWithHooks(
|
||||||
ctx,
|
ctx,
|
||||||
envVars,
|
envVars,
|
||||||
repoPath,
|
repoPath,
|
||||||
@ -234,29 +306,29 @@ func (a Adapter) UpdateRef(
|
|||||||
// updateRefWithHooks performs a git-ref update for the provided reference.
|
// updateRefWithHooks performs a git-ref update for the provided reference.
|
||||||
// Requires both old and new value to be provided explcitly, or the call fails (ensures consistency across operation).
|
// Requires both old and new value to be provided explcitly, or the call fails (ensures consistency across operation).
|
||||||
// pre-receice will be called before the update, post-receive after.
|
// pre-receice will be called before the update, post-receive after.
|
||||||
func (a Adapter) updateRefWithHooks(
|
func (g *Git) updateRefWithHooks(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
envVars map[string]string,
|
envVars map[string]string,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
ref string,
|
ref string,
|
||||||
oldValue string,
|
oldValue sha.SHA,
|
||||||
newValue string,
|
newValue sha.SHA,
|
||||||
) error {
|
) error {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return ErrRepositoryPathEmpty
|
return ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldValue == "" {
|
if oldValue.IsEmpty() {
|
||||||
return fmt.Errorf("oldValue can't be empty")
|
return fmt.Errorf("oldValue can't be empty")
|
||||||
}
|
}
|
||||||
if newValue == "" {
|
if newValue.IsEmpty() {
|
||||||
return fmt.Errorf("newValue can't be empty")
|
return fmt.Errorf("newValue can't be empty")
|
||||||
}
|
}
|
||||||
if oldValue == types.NilSHA && newValue == types.NilSHA {
|
if oldValue.IsNil() && newValue.IsNil() {
|
||||||
return fmt.Errorf("provided values cannot be both empty")
|
return fmt.Errorf("provided values cannot be both empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
githookClient, err := a.githookFactory.NewClient(ctx, envVars)
|
githookClient, err := g.githookFactory.NewClient(ctx, envVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create githook client: %w", err)
|
return fmt.Errorf("failed to create githook client: %w", err)
|
||||||
}
|
}
|
||||||
@ -278,28 +350,23 @@ func (a Adapter) updateRefWithHooks(
|
|||||||
return fmt.Errorf("pre-receive call returned error: %q", *out.Error)
|
return fmt.Errorf("pre-receive call returned error: %q", *out.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.traceGit {
|
if g.traceGit {
|
||||||
log.Ctx(ctx).Trace().
|
log.Ctx(ctx).Trace().
|
||||||
Str("git", "pre-receive").
|
Str("git", "pre-receive").
|
||||||
Msgf("pre-receive call succeeded with output:\n%s", strings.Join(out.Messages, "\n"))
|
Msgf("pre-receive call succeeded with output:\n%s", strings.Join(out.Messages, "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
args := make([]string, 0, 4)
|
cmd := command.New("update-ref")
|
||||||
args = append(args, "update-ref")
|
if newValue.IsNil() {
|
||||||
if newValue == types.NilSHA {
|
cmd.Add(command.WithFlag("-d", ref))
|
||||||
args = append(args, "-d", ref)
|
|
||||||
} else {
|
} else {
|
||||||
args = append(args, ref, newValue)
|
cmd.Add(command.WithArg(ref, newValue.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, oldValue)
|
cmd.Add(command.WithArg(oldValue.String()))
|
||||||
|
err = cmd.Run(ctx, command.WithDir(repoPath))
|
||||||
cmd := gitea.NewCommand(ctx, args...)
|
|
||||||
_, _, err = cmd.RunStdString(&gitea.RunOpts{
|
|
||||||
Dir: repoPath,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return processGiteaErrorf(err, "update of ref %q from %q to %q failed", ref, oldValue, newValue)
|
return processGitErrorf(err, "update of ref %q from %q to %q failed", ref, oldValue, newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// call post-receive after updating the reference
|
// call post-receive after updating the reference
|
||||||
@ -319,7 +386,7 @@ func (a Adapter) updateRefWithHooks(
|
|||||||
return fmt.Errorf("post-receive call returned error: %q", *out.Error)
|
return fmt.Errorf("post-receive call returned error: %q", *out.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.traceGit {
|
if g.traceGit {
|
||||||
log.Ctx(ctx).Trace().
|
log.Ctx(ctx).Trace().
|
||||||
Str("git", "post-receive").
|
Str("git", "post-receive").
|
||||||
Msgf("post-receive call succeeded with output:\n%s", strings.Join(out.Messages, "\n"))
|
Msgf("post-receive call succeeded with output:\n%s", strings.Join(out.Messages, "\n"))
|
@ -12,23 +12,59 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/command"
|
"github.com/harness/gitness/git/command"
|
||||||
"github.com/harness/gitness/git/types"
|
|
||||||
|
|
||||||
gitea "code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CloneRepoOptions struct {
|
||||||
|
Timeout time.Duration
|
||||||
|
Mirror bool
|
||||||
|
Bare bool
|
||||||
|
Quiet bool
|
||||||
|
Branch string
|
||||||
|
Shared bool
|
||||||
|
NoCheckout bool
|
||||||
|
Depth int
|
||||||
|
Filter string
|
||||||
|
SkipTLSVerify bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type PushOptions struct {
|
||||||
|
Remote string
|
||||||
|
Branch string
|
||||||
|
Force bool
|
||||||
|
ForceWithLease string
|
||||||
|
Env []string
|
||||||
|
Timeout time.Duration
|
||||||
|
Mirror bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectCount represents the parsed information from the `git count-objects -v` command.
|
||||||
|
// For field meanings, see https://git-scm.com/docs/git-count-objects#_options.
|
||||||
|
type ObjectCount struct {
|
||||||
|
Count int
|
||||||
|
Size int64
|
||||||
|
InPack int
|
||||||
|
Packs int
|
||||||
|
SizePack int64
|
||||||
|
PrunePackable int
|
||||||
|
Garbage int
|
||||||
|
SizeGarbage int64
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
gitReferenceNamePrefixBranch = "refs/heads/"
|
gitReferenceNamePrefixBranch = "refs/heads/"
|
||||||
gitReferenceNamePrefixTag = "refs/tags/"
|
gitReferenceNamePrefixTag = "refs/tags/"
|
||||||
@ -37,7 +73,7 @@ const (
|
|||||||
var lsRemoteHeadRegexp = regexp.MustCompile(`ref: refs/heads/([^\s]+)\s+HEAD`)
|
var lsRemoteHeadRegexp = regexp.MustCompile(`ref: refs/heads/([^\s]+)\s+HEAD`)
|
||||||
|
|
||||||
// InitRepository initializes a new Git repository.
|
// InitRepository initializes a new Git repository.
|
||||||
func (a Adapter) InitRepository(
|
func (g *Git) InitRepository(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
bare bool,
|
bare bool,
|
||||||
@ -57,19 +93,8 @@ func (a Adapter) InitRepository(
|
|||||||
return cmd.Run(ctx, command.WithDir(repoPath))
|
return cmd.Run(ctx, command.WithDir(repoPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Adapter) OpenRepository(
|
|
||||||
ctx context.Context,
|
|
||||||
repoPath string,
|
|
||||||
) (*gitea.Repository, error) {
|
|
||||||
repo, err := gitea.OpenRepository(ctx, repoPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, processGiteaErrorf(err, "failed to open repository")
|
|
||||||
}
|
|
||||||
return repo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDefaultBranch sets the default branch of a repo.
|
// SetDefaultBranch sets the default branch of a repo.
|
||||||
func (a Adapter) SetDefaultBranch(
|
func (g *Git) SetDefaultBranch(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
defaultBranch string,
|
defaultBranch string,
|
||||||
@ -78,102 +103,124 @@ func (a Adapter) SetDefaultBranch(
|
|||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return ErrRepositoryPathEmpty
|
return ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
giteaRepo, err := gitea.OpenRepository(ctx, repoPath)
|
|
||||||
if err != nil {
|
|
||||||
return processGiteaErrorf(err, "failed to open repository")
|
|
||||||
}
|
|
||||||
defer giteaRepo.Close()
|
|
||||||
|
|
||||||
// if requested, error out if branch doesn't exist. Otherwise, blindly set it.
|
// if requested, error out if branch doesn't exist. Otherwise, blindly set it.
|
||||||
if !allowEmpty && !giteaRepo.IsBranchExist(defaultBranch) {
|
exist, err := g.IsBranchExist(ctx, repoPath, defaultBranch)
|
||||||
|
if err != nil {
|
||||||
|
log.Ctx(ctx).Err(err).Msgf("failed to set default branch")
|
||||||
|
}
|
||||||
|
if !allowEmpty && !exist {
|
||||||
// TODO: ensure this returns not found error to caller
|
// TODO: ensure this returns not found error to caller
|
||||||
return fmt.Errorf("branch '%s' does not exist", defaultBranch)
|
return fmt.Errorf("branch '%s' does not exist", defaultBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// change default branch
|
// change default branch
|
||||||
err = giteaRepo.SetDefaultBranch(defaultBranch)
|
cmd := command.New("symbolic-ref",
|
||||||
|
command.WithArg("HEAD", gitReferenceNamePrefixBranch+defaultBranch),
|
||||||
|
)
|
||||||
|
err = cmd.Run(ctx, command.WithDir(repoPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return processGiteaErrorf(err, "failed to set new default branch")
|
return processGitErrorf(err, "failed to set new default branch")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultBranch gets the default branch of a repo.
|
// GetDefaultBranch gets the default branch of a repo.
|
||||||
func (a Adapter) GetDefaultBranch(
|
func (g *Git) GetDefaultBranch(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return "", ErrRepositoryPathEmpty
|
return "", ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
giteaRepo, err := gitea.OpenRepository(ctx, repoPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", processGiteaErrorf(err, "failed to open gitea repo")
|
|
||||||
}
|
|
||||||
defer giteaRepo.Close()
|
|
||||||
|
|
||||||
// get default branch
|
// get default branch
|
||||||
branch, err := giteaRepo.GetDefaultBranch()
|
cmd := command.New("symbolic-ref",
|
||||||
|
command.WithArg("HEAD"),
|
||||||
|
)
|
||||||
|
output := &bytes.Buffer{}
|
||||||
|
err := cmd.Run(ctx,
|
||||||
|
command.WithDir(repoPath),
|
||||||
|
command.WithStdout(output))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", processGiteaErrorf(err, "failed to get default branch")
|
return "", processGitErrorf(err, "failed to get default branch")
|
||||||
}
|
}
|
||||||
|
|
||||||
return branch, nil
|
return output.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRemoteDefaultBranch retrieves the default branch of a remote repository.
|
// GetRemoteDefaultBranch retrieves the default branch of a remote repository.
|
||||||
// If the repo doesn't have a default branch, types.ErrNoDefaultBranch is returned.
|
// If the repo doesn't have a default branch, types.ErrNoDefaultBranch is returned.
|
||||||
func (a Adapter) GetRemoteDefaultBranch(
|
func (g *Git) GetRemoteDefaultBranch(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
remoteURL string,
|
remoteURL string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
args := []string{
|
cmd := command.New("ls-remote",
|
||||||
"-c", "credential.helper=",
|
command.WithConfig("credential.helper", ""),
|
||||||
"ls-remote",
|
command.WithFlag("--symref"),
|
||||||
"--symref",
|
command.WithFlag("-q"),
|
||||||
"-q",
|
command.WithArg(remoteURL),
|
||||||
remoteURL,
|
command.WithArg("HEAD"),
|
||||||
"HEAD",
|
)
|
||||||
}
|
output := &bytes.Buffer{}
|
||||||
|
if err := cmd.Run(ctx, command.WithStdout(output)); err != nil {
|
||||||
cmd := gitea.NewCommand(ctx, args...)
|
return "", processGitErrorf(err, "failed to ls remote repo")
|
||||||
stdOut, _, err := cmd.RunStdString(nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", processGiteaErrorf(err, "failed to ls remote repo")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// git output looks as follows, and we are looking for the ref that HEAD points to
|
// git output looks as follows, and we are looking for the ref that HEAD points to
|
||||||
// ref: refs/heads/main HEAD
|
// ref: refs/heads/main HEAD
|
||||||
// 46963bc7f0b5e8c5f039d50ac9e6e51933c78cdf HEAD
|
// 46963bc7f0b5e8c5f039d50ac9e6e51933c78cdf HEAD
|
||||||
match := lsRemoteHeadRegexp.FindStringSubmatch(stdOut)
|
match := lsRemoteHeadRegexp.FindStringSubmatch(strings.TrimSpace(output.String()))
|
||||||
if match == nil {
|
if match == nil {
|
||||||
return "", types.ErrNoDefaultBranch
|
return "", ErrNoDefaultBranch
|
||||||
}
|
}
|
||||||
|
|
||||||
return match[1], nil
|
return match[1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Adapter) Clone(
|
func (g *Git) Clone(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
from string,
|
from string,
|
||||||
to string,
|
to string,
|
||||||
opts types.CloneRepoOptions,
|
opts CloneRepoOptions,
|
||||||
) error {
|
) error {
|
||||||
err := gitea.Clone(ctx, from, to, gitea.CloneRepoOptions{
|
if err := os.MkdirAll(to, os.ModePerm); err != nil {
|
||||||
Timeout: opts.Timeout,
|
return err
|
||||||
Mirror: opts.Mirror,
|
}
|
||||||
Bare: opts.Bare,
|
|
||||||
Quiet: opts.Quiet,
|
cmd := command.New("clone")
|
||||||
Branch: opts.Branch,
|
if opts.SkipTLSVerify {
|
||||||
Shared: opts.Shared,
|
cmd.Add(command.WithConfig("http.sslVerify", "false"))
|
||||||
NoCheckout: opts.NoCheckout,
|
}
|
||||||
Depth: opts.Depth,
|
if opts.Mirror {
|
||||||
Filter: opts.Filter,
|
cmd.Add(command.WithFlag("--mirror"))
|
||||||
SkipTLSVerify: opts.SkipTLSVerify,
|
}
|
||||||
})
|
if opts.Bare {
|
||||||
if err != nil {
|
cmd.Add(command.WithFlag("--bare"))
|
||||||
return processGiteaErrorf(err, "failed to clone repo")
|
}
|
||||||
|
if opts.Quiet {
|
||||||
|
cmd.Add(command.WithFlag("--quiet"))
|
||||||
|
}
|
||||||
|
if opts.Shared {
|
||||||
|
cmd.Add(command.WithFlag("-s"))
|
||||||
|
}
|
||||||
|
if opts.NoCheckout {
|
||||||
|
cmd.Add(command.WithFlag("--no-checkout"))
|
||||||
|
}
|
||||||
|
if opts.Depth > 0 {
|
||||||
|
cmd.Add(command.WithFlag("--depth", strconv.Itoa(opts.Depth)))
|
||||||
|
}
|
||||||
|
if opts.Filter != "" {
|
||||||
|
cmd.Add(command.WithFlag("--filter", opts.Filter))
|
||||||
|
}
|
||||||
|
if len(opts.Branch) > 0 {
|
||||||
|
cmd.Add(command.WithFlag("-b", opts.Branch))
|
||||||
|
}
|
||||||
|
cmd.Add(command.WithPostSepArg(from, to))
|
||||||
|
|
||||||
|
if err := cmd.Run(ctx); err != nil {
|
||||||
|
return fmt.Errorf("failed to clone repository: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -181,7 +228,7 @@ func (a Adapter) Clone(
|
|||||||
|
|
||||||
// Sync synchronizes the repository to match the provided source.
|
// Sync synchronizes the repository to match the provided source.
|
||||||
// NOTE: This is a read operation and doesn't trigger any server side hooks.
|
// NOTE: This is a read operation and doesn't trigger any server side hooks.
|
||||||
func (a Adapter) Sync(
|
func (g *Git) Sync(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
source string,
|
source string,
|
||||||
@ -193,33 +240,31 @@ func (a Adapter) Sync(
|
|||||||
if len(refSpecs) == 0 {
|
if len(refSpecs) == 0 {
|
||||||
refSpecs = []string{"+refs/*:refs/*"}
|
refSpecs = []string{"+refs/*:refs/*"}
|
||||||
}
|
}
|
||||||
args := []string{
|
cmd := command.New("fetch",
|
||||||
"-c", "advice.fetchShowForcedUpdates=false",
|
command.WithConfig("advice.fetchShowForcedUpdates", "false"),
|
||||||
"-c", "credential.helper=",
|
command.WithConfig("credential.helper", ""),
|
||||||
"fetch",
|
command.WithFlag(
|
||||||
"--quiet",
|
"--quiet",
|
||||||
"--prune",
|
"--prune",
|
||||||
"--atomic",
|
"--atomic",
|
||||||
"--force",
|
"--force",
|
||||||
"--no-write-fetch-head",
|
"--no-write-fetch-head",
|
||||||
"--no-show-forced-updates",
|
"--no-show-forced-updates",
|
||||||
source,
|
),
|
||||||
}
|
command.WithArg(source),
|
||||||
args = append(args, refSpecs...)
|
command.WithArg(refSpecs...),
|
||||||
|
)
|
||||||
|
|
||||||
cmd := gitea.NewCommand(ctx, args...)
|
err := cmd.Run(ctx, command.WithDir(repoPath))
|
||||||
_, _, err := cmd.RunStdString(&gitea.RunOpts{
|
|
||||||
Dir: repoPath,
|
|
||||||
UseContextTimeout: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return processGiteaErrorf(err, "failed to sync repo")
|
return processGitErrorf(err, "failed to sync repo")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Adapter) AddFiles(
|
func (g *Git) AddFiles(
|
||||||
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
all bool,
|
all bool,
|
||||||
files ...string,
|
files ...string,
|
||||||
@ -227,20 +272,26 @@ func (a Adapter) AddFiles(
|
|||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return ErrRepositoryPathEmpty
|
return ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
err := gitea.AddChanges(repoPath, all, files...)
|
|
||||||
|
cmd := command.New("add")
|
||||||
|
if all {
|
||||||
|
cmd.Add(command.WithFlag("--all"))
|
||||||
|
}
|
||||||
|
cmd.Add(command.WithPostSepArg(files...))
|
||||||
|
|
||||||
|
err := cmd.Run(ctx, command.WithDir(repoPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return processGiteaErrorf(err, "failed to add changes")
|
return processGitErrorf(err, "failed to add changes")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit commits the changes of the repository.
|
// Commit commits the changes of the repository.
|
||||||
// NOTE: Modification of gitea implementation that supports commiter_date + author_date.
|
func (g *Git) Commit(
|
||||||
func (a Adapter) Commit(
|
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
opts types.CommitChangesOptions,
|
opts CommitChangesOptions,
|
||||||
) error {
|
) error {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return ErrRepositoryPathEmpty
|
return ErrRepositoryPathEmpty
|
||||||
@ -262,75 +313,57 @@ func (a Adapter) Commit(
|
|||||||
err := cmd.Run(ctx, command.WithDir(repoPath))
|
err := cmd.Run(ctx, command.WithDir(repoPath))
|
||||||
// No stderr but exit status 1 means nothing to commit (see gitea CommitChanges)
|
// No stderr but exit status 1 means nothing to commit (see gitea CommitChanges)
|
||||||
if err != nil && err.Error() != "exit status 1" {
|
if err != nil && err.Error() != "exit status 1" {
|
||||||
return processGiteaErrorf(err, "failed to commit changes")
|
return processGitErrorf(err, "failed to commit changes")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push pushs local commits to given remote branch.
|
// Push pushs local commits to given remote branch.
|
||||||
// NOTE: Modification of gitea implementation that supports --force-with-lease.
|
// TODOD: return our own error types and move to above api.Push method
|
||||||
// TODOD: return our own error types and move to above adapter.Push method
|
func (g *Git) Push(
|
||||||
func (a Adapter) Push(
|
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
opts types.PushOptions,
|
opts PushOptions,
|
||||||
) error {
|
) error {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return ErrRepositoryPathEmpty
|
return ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
cmd := gitea.NewCommand(ctx,
|
cmd := command.New("push",
|
||||||
"-c", "credential.helper=",
|
command.WithConfig("credential.helper", ""),
|
||||||
"push",
|
|
||||||
)
|
)
|
||||||
if opts.Force {
|
if opts.Force {
|
||||||
cmd.AddArguments("-f")
|
cmd.Add(command.WithFlag("-f"))
|
||||||
}
|
}
|
||||||
if opts.ForceWithLease != "" {
|
if opts.ForceWithLease != "" {
|
||||||
cmd.AddArguments(fmt.Sprintf("--force-with-lease=%s", opts.ForceWithLease))
|
cmd.Add(command.WithFlag("--force-with-lease=" + opts.ForceWithLease))
|
||||||
}
|
}
|
||||||
if opts.Mirror {
|
if opts.Mirror {
|
||||||
cmd.AddArguments("--mirror")
|
cmd.Add(command.WithFlag("--mirror"))
|
||||||
}
|
}
|
||||||
cmd.AddArguments("--", opts.Remote)
|
cmd.Add(command.WithPostSepArg(opts.Remote))
|
||||||
|
|
||||||
if len(opts.Branch) > 0 {
|
if len(opts.Branch) > 0 {
|
||||||
cmd.AddArguments(opts.Branch)
|
cmd.Add(command.WithPostSepArg(opts.Branch))
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.traceGit {
|
||||||
|
cmd.Add(command.WithEnv(command.GitTrace, "true"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove credentials if there are any
|
// remove credentials if there are any
|
||||||
if strings.Contains(opts.Remote, "://") && strings.Contains(opts.Remote, "@") {
|
if strings.Contains(opts.Remote, "://") && strings.Contains(opts.Remote, "@") {
|
||||||
opts.Remote = util.SanitizeCredentialURLs(opts.Remote)
|
opts.Remote = SanitizeCredentialURLs(opts.Remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Timeout == 0 {
|
|
||||||
opts.Timeout = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.traceGit {
|
|
||||||
// create copy to not modify original underlying array
|
|
||||||
opts.Env = append([]string{gitTrace + "=true"}, opts.Env...)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.SetDescription(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"pushing %s to %s (Force: %t, ForceWithLease: %s)",
|
|
||||||
opts.Branch,
|
|
||||||
opts.Remote,
|
|
||||||
opts.Force,
|
|
||||||
opts.ForceWithLease,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
var outbuf, errbuf strings.Builder
|
var outbuf, errbuf strings.Builder
|
||||||
err := cmd.Run(&gitea.RunOpts{
|
err := cmd.Run(ctx,
|
||||||
Env: opts.Env,
|
command.WithDir(repoPath),
|
||||||
Timeout: opts.Timeout,
|
command.WithStdout(&outbuf),
|
||||||
Dir: repoPath,
|
command.WithStderr(&errbuf),
|
||||||
Stdout: &outbuf,
|
command.WithEnvs(opts.Env...),
|
||||||
Stderr: &errbuf,
|
)
|
||||||
})
|
|
||||||
|
|
||||||
if a.traceGit {
|
if g.traceGit {
|
||||||
log.Ctx(ctx).Trace().
|
log.Ctx(ctx).Trace().
|
||||||
Str("git", "push").
|
Str("git", "push").
|
||||||
Err(err).
|
Err(err).
|
||||||
@ -340,13 +373,13 @@ func (a Adapter) Push(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(errbuf.String(), "non-fast-forward"):
|
case strings.Contains(errbuf.String(), "non-fast-forward"):
|
||||||
return &gitea.ErrPushOutOfDate{
|
return &PushOutOfDateError{
|
||||||
StdOut: outbuf.String(),
|
StdOut: outbuf.String(),
|
||||||
StdErr: errbuf.String(),
|
StdErr: errbuf.String(),
|
||||||
Err: err,
|
Err: err,
|
||||||
}
|
}
|
||||||
case strings.Contains(errbuf.String(), "! [remote rejected]"):
|
case strings.Contains(errbuf.String(), "! [remote rejected]"):
|
||||||
err := &gitea.ErrPushRejected{
|
err := &PushRejectedError{
|
||||||
StdOut: outbuf.String(),
|
StdOut: outbuf.String(),
|
||||||
StdErr: errbuf.String(),
|
StdErr: errbuf.String(),
|
||||||
Err: err,
|
Err: err,
|
||||||
@ -354,7 +387,7 @@ func (a Adapter) Push(
|
|||||||
err.GenerateMessage()
|
err.GenerateMessage()
|
||||||
return err
|
return err
|
||||||
case strings.Contains(errbuf.String(), "matches more than one"):
|
case strings.Contains(errbuf.String(), "matches more than one"):
|
||||||
err := &gitea.ErrMoreThanOne{
|
err := &MoreThanOneError{
|
||||||
StdOut: outbuf.String(),
|
StdOut: outbuf.String(),
|
||||||
StdErr: errbuf.String(),
|
StdErr: errbuf.String(),
|
||||||
Err: err,
|
Err: err,
|
||||||
@ -371,31 +404,29 @@ func (a Adapter) Push(
|
|||||||
err = fmt.Errorf("%w\ncmd error output: %s", err, errbuf.String())
|
err = fmt.Errorf("%w\ncmd error output: %s", err, errbuf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return processGiteaErrorf(err, "failed to push changes")
|
return processGitErrorf(err, "failed to push changes")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Adapter) CountObjects(ctx context.Context, repoPath string) (types.ObjectCount, error) {
|
func (g *Git) CountObjects(ctx context.Context, repoPath string) (ObjectCount, error) {
|
||||||
cmd := gitea.NewCommand(ctx,
|
|
||||||
"count-objects", "-v",
|
|
||||||
)
|
|
||||||
|
|
||||||
var outbuf strings.Builder
|
var outbuf strings.Builder
|
||||||
if err := cmd.Run(&gitea.RunOpts{
|
cmd := command.New("count-objects", command.WithFlag("-v"))
|
||||||
Dir: repoPath,
|
err := cmd.Run(ctx,
|
||||||
Stdout: &outbuf,
|
command.WithDir(repoPath),
|
||||||
}); err != nil {
|
command.WithStdout(&outbuf),
|
||||||
return types.ObjectCount{}, fmt.Errorf("error running git count-objects: %w", err)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return ObjectCount{}, fmt.Errorf("error running git count-objects: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objectCount := parseGitCountObjectsOutput(ctx, outbuf.String())
|
objectCount := parseGitCountObjectsOutput(ctx, outbuf.String())
|
||||||
return objectCount, nil
|
return objectCount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseGitCountObjectsOutput(ctx context.Context, output string) types.ObjectCount {
|
func parseGitCountObjectsOutput(ctx context.Context, output string) ObjectCount {
|
||||||
info := types.ObjectCount{}
|
info := ObjectCount{}
|
||||||
|
|
||||||
output = strings.TrimSpace(output)
|
output = strings.TrimSpace(output)
|
||||||
lines := strings.Split(output, "\n")
|
lines := strings.Split(output, "\n")
|
@ -12,32 +12,31 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
|
"github.com/harness/gitness/git/command"
|
||||||
gitea "code.gitea.io/gitea/modules/git"
|
"github.com/harness/gitness/git/sha"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a Adapter) ResolveRev(ctx context.Context,
|
func (g *Git) ResolveRev(ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
rev string,
|
rev string,
|
||||||
) (string, error) {
|
) (sha.SHA, error) {
|
||||||
args := []string{"rev-parse", rev}
|
cmd := command.New("rev-parse", command.WithArg(rev))
|
||||||
commitSHA, stdErr, err := gitea.NewCommand(ctx, args...).RunStdString(&gitea.RunOpts{Dir: repoPath})
|
output := &bytes.Buffer{}
|
||||||
if strings.Contains(stdErr, "ambiguous argument") {
|
err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(output))
|
||||||
return "", errors.InvalidArgument("could not resolve git revision: %s", rev)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to resolve git revision: %w", err)
|
if strings.Contains(err.Error(), "ambiguous argument") {
|
||||||
|
return sha.None, errors.InvalidArgument("could not resolve git revision: %s", rev)
|
||||||
|
}
|
||||||
|
return sha.None, fmt.Errorf("failed to resolve git revision: %w", err)
|
||||||
}
|
}
|
||||||
|
return sha.New(output.String())
|
||||||
commitSHA = strings.TrimSpace(commitSHA)
|
|
||||||
|
|
||||||
return commitSHA, nil
|
|
||||||
}
|
}
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -29,25 +29,24 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
|
"github.com/harness/gitness/git/command"
|
||||||
|
"github.com/harness/gitness/git/sha"
|
||||||
"github.com/harness/gitness/git/tempdir"
|
"github.com/harness/gitness/git/tempdir"
|
||||||
"github.com/harness/gitness/git/types"
|
|
||||||
|
|
||||||
gitea "code.gitea.io/gitea/modules/git"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SharedRepo is a type to wrap our upload repositories as a shallow clone.
|
// SharedRepo is a type to wrap our upload repositories as a shallow clone.
|
||||||
type SharedRepo struct {
|
type SharedRepo struct {
|
||||||
adapter Adapter
|
git *Git
|
||||||
repoUID string
|
repoUID string
|
||||||
repo *gitea.Repository
|
|
||||||
remoteRepoPath string
|
remoteRepoPath string
|
||||||
tmpPath string
|
RepoPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSharedRepo creates a new temporary upload repository.
|
// NewSharedRepo creates a new temporary upload repository.
|
||||||
func NewSharedRepo(
|
func NewSharedRepo(
|
||||||
adapter Adapter,
|
adapter *Git,
|
||||||
baseTmpDir string,
|
baseTmpDir string,
|
||||||
repoUID string,
|
repoUID string,
|
||||||
remoteRepoPath string,
|
remoteRepoPath string,
|
||||||
@ -58,27 +57,18 @@ func NewSharedRepo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
t := &SharedRepo{
|
t := &SharedRepo{
|
||||||
adapter: adapter,
|
git: adapter,
|
||||||
repoUID: repoUID,
|
repoUID: repoUID,
|
||||||
remoteRepoPath: remoteRepoPath,
|
remoteRepoPath: remoteRepoPath,
|
||||||
tmpPath: tmpPath,
|
RepoPath: tmpPath,
|
||||||
}
|
}
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SharedRepo) Path() string {
|
|
||||||
return r.repo.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *SharedRepo) RemotePath() string {
|
|
||||||
return r.remoteRepoPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the repository cleaning up all files.
|
// Close the repository cleaning up all files.
|
||||||
func (r *SharedRepo) Close(ctx context.Context) {
|
func (r *SharedRepo) Close(ctx context.Context) {
|
||||||
defer r.repo.Close()
|
if err := tempdir.RemoveTemporaryPath(r.RepoPath); err != nil {
|
||||||
if err := tempdir.RemoveTemporaryPath(r.tmpPath); err != nil {
|
log.Ctx(ctx).Err(err).Msgf("Failed to remove temporary path %s", r.RepoPath)
|
||||||
log.Ctx(ctx).Err(err).Msgf("Failed to remove temporary path %s", r.tmpPath)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +98,7 @@ type fileEntry struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *SharedRepo) MoveObjects(ctx context.Context) error {
|
func (r *SharedRepo) MoveObjects(ctx context.Context) error {
|
||||||
srcDir := path.Join(r.tmpPath, "objects")
|
srcDir := path.Join(r.RepoPath, "objects")
|
||||||
dstDir := path.Join(r.remoteRepoPath, "objects")
|
dstDir := path.Join(r.remoteRepoPath, "objects")
|
||||||
|
|
||||||
var files []fileEntry
|
var files []fileEntry
|
||||||
@ -209,15 +199,13 @@ func (r *SharedRepo) MoveObjects(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *SharedRepo) InitAsShared(ctx context.Context) error {
|
func (r *SharedRepo) InitAsShared(ctx context.Context) error {
|
||||||
args := []string{"init", "--bare"}
|
cmd := command.New("init", command.WithFlag("--bare"))
|
||||||
if _, stderr, err := gitea.NewCommand(ctx, args...).RunStdString(&gitea.RunOpts{
|
if err := cmd.Run(ctx, command.WithDir(r.RepoPath)); err != nil {
|
||||||
Dir: r.tmpPath,
|
return errors.Internal(err, "error while creating empty repository")
|
||||||
}); err != nil {
|
|
||||||
return errors.Internal(err, "error while creating empty repository: %s", stderr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := func() error {
|
if err := func() error {
|
||||||
alternates := filepath.Join(r.tmpPath, "objects", "info", "alternates")
|
alternates := filepath.Join(r.RepoPath, "objects", "info", "alternates")
|
||||||
f, err := os.OpenFile(alternates, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
|
f, err := os.OpenFile(alternates, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to open alternates file '%s': %w", alternates, err)
|
return fmt.Errorf("failed to open alternates file '%s': %w", alternates, err)
|
||||||
@ -234,66 +222,57 @@ func (r *SharedRepo) InitAsShared(ctx context.Context) error {
|
|||||||
return errors.Internal(err, "failed to create alternate in empty repository: %s", err.Error())
|
return errors.Internal(err, "failed to create alternate in empty repository: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
gitRepo, err := gitea.OpenRepository(ctx, r.tmpPath)
|
|
||||||
if err != nil {
|
|
||||||
return processGiteaErrorf(err, "failed to open repo")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.repo = gitRepo
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone the base repository to our path and set branch as the HEAD.
|
// Clone the base repository to our path and set branch as the HEAD.
|
||||||
func (r *SharedRepo) Clone(ctx context.Context, branchName string) error {
|
func (r *SharedRepo) Clone(ctx context.Context, branchName string) error {
|
||||||
args := []string{"clone", "-s", "--bare"}
|
cmd := command.New("clone",
|
||||||
|
command.WithFlag("-s"),
|
||||||
|
command.WithFlag("--bare"),
|
||||||
|
)
|
||||||
if branchName != "" {
|
if branchName != "" {
|
||||||
args = append(args, "-b", strings.TrimPrefix(branchName, gitReferenceNamePrefixBranch))
|
cmd.Add(command.WithFlag("-b", strings.TrimPrefix(branchName, gitReferenceNamePrefixBranch)))
|
||||||
}
|
}
|
||||||
args = append(args, r.remoteRepoPath, r.tmpPath)
|
cmd.Add(command.WithArg(r.remoteRepoPath, r.RepoPath))
|
||||||
|
|
||||||
if _, _, err := gitea.NewCommand(ctx, args...).RunStdString(nil); err != nil {
|
if err := cmd.Run(ctx); err != nil {
|
||||||
stderr := err.Error()
|
cmderr := command.AsError(err)
|
||||||
if matched, _ := regexp.MatchString(".*Remote branch .* not found in upstream origin.*", stderr); matched {
|
if cmderr.StdErr == nil {
|
||||||
|
return errors.Internal(err, "error while cloning repository")
|
||||||
|
}
|
||||||
|
stderr := string(cmderr.StdErr)
|
||||||
|
matched, _ := regexp.MatchString(".*Remote branch .* not found in upstream origin.*", stderr)
|
||||||
|
if matched {
|
||||||
return errors.NotFound("branch '%s' does not exist", branchName)
|
return errors.NotFound("branch '%s' does not exist", branchName)
|
||||||
} else if matched, _ = regexp.MatchString(".* repository .* does not exist.*", stderr); matched {
|
}
|
||||||
|
matched, _ = regexp.MatchString(".* repository .* does not exist.*", stderr)
|
||||||
|
if matched {
|
||||||
return errors.NotFound("repository '%s' does not exist", r.repoUID)
|
return errors.NotFound("repository '%s' does not exist", r.repoUID)
|
||||||
}
|
}
|
||||||
return errors.Internal(nil, "error while cloning repository: %s", stderr)
|
|
||||||
}
|
}
|
||||||
gitRepo, err := gitea.OpenRepository(ctx, r.tmpPath)
|
|
||||||
if err != nil {
|
|
||||||
return processGiteaErrorf(err, "failed to open repo")
|
|
||||||
}
|
|
||||||
r.repo = gitRepo
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init the repository.
|
// Init the repository.
|
||||||
func (r *SharedRepo) Init(ctx context.Context) error {
|
func (r *SharedRepo) Init(ctx context.Context) error {
|
||||||
if err := gitea.InitRepository(ctx, r.tmpPath, false); err != nil {
|
err := r.git.InitRepository(ctx, r.RepoPath, false)
|
||||||
return err
|
|
||||||
}
|
|
||||||
gitRepo, err := gitea.OpenRepository(ctx, r.tmpPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return processGiteaErrorf(err, "failed to open repo")
|
return fmt.Errorf("failed to initialize shared repo: %w", err)
|
||||||
}
|
}
|
||||||
r.repo = gitRepo
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaultIndex sets the git index to our HEAD.
|
// SetDefaultIndex sets the git index to our HEAD.
|
||||||
func (r *SharedRepo) SetDefaultIndex(ctx context.Context) error {
|
func (r *SharedRepo) SetDefaultIndex(ctx context.Context) error {
|
||||||
if _, _, err := gitea.NewCommand(ctx, "read-tree", "HEAD").RunStdString(&gitea.RunOpts{Dir: r.tmpPath}); err != nil {
|
return r.SetIndex(ctx, "HEAD")
|
||||||
return fmt.Errorf("failed to git read-tree HEAD: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetIndex sets the git index to the provided treeish.
|
// SetIndex sets the git index to the provided treeish.
|
||||||
func (r *SharedRepo) SetIndex(ctx context.Context, treeish string) error {
|
func (r *SharedRepo) SetIndex(ctx context.Context, rev string) error {
|
||||||
if _, _, err := gitea.NewCommand(ctx, "read-tree", treeish).RunStdString(&gitea.RunOpts{Dir: r.tmpPath}); err != nil {
|
cmd := command.New("read-tree", command.WithArg(rev))
|
||||||
return fmt.Errorf("failed to git read-tree %s: %w", treeish, err)
|
if err := cmd.Run(ctx, command.WithDir(r.RepoPath)); err != nil {
|
||||||
|
return fmt.Errorf("failed to git read-tree %s: %w", rev, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -303,33 +282,32 @@ func (r *SharedRepo) LsFiles(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
filenames ...string,
|
filenames ...string,
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
stdOut := new(bytes.Buffer)
|
cmd := command.New("ls-files",
|
||||||
stdErr := new(bytes.Buffer)
|
command.WithFlag("-z"),
|
||||||
|
)
|
||||||
|
|
||||||
cmdArgs := []string{"ls-files", "-z", "--"}
|
|
||||||
for _, arg := range filenames {
|
for _, arg := range filenames {
|
||||||
if arg != "" {
|
if arg != "" {
|
||||||
cmdArgs = append(cmdArgs, arg)
|
cmd.Add(command.WithPostSepArg(arg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gitea.NewCommand(ctx, cmdArgs...).
|
stdout := bytes.NewBuffer(nil)
|
||||||
Run(&gitea.RunOpts{
|
|
||||||
Dir: r.tmpPath,
|
err := cmd.Run(ctx,
|
||||||
Stdout: stdOut,
|
command.WithDir(r.RepoPath),
|
||||||
Stderr: stdErr,
|
command.WithStdout(stdout),
|
||||||
}); err != nil {
|
)
|
||||||
return nil, fmt.Errorf("unable to run git ls-files for temporary repo of: "+
|
if err != nil {
|
||||||
"%s Error: %w\nstdout: %s\nstderr: %s",
|
return nil, fmt.Errorf("failed to list files in shared repository's git index: %w", err)
|
||||||
r.repoUID, err, stdOut.String(), stdErr.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filelist := make([]string, 0)
|
files := make([]string, 0)
|
||||||
for _, line := range bytes.Split(stdOut.Bytes(), []byte{'\000'}) {
|
for _, line := range bytes.Split(stdout.Bytes(), []byte{'\000'}) {
|
||||||
filelist = append(filelist, string(line))
|
files = append(files, string(line))
|
||||||
}
|
}
|
||||||
|
|
||||||
return filelist, nil
|
return files, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveFilesFromIndex removes the given files from the index.
|
// RemoveFilesFromIndex removes the given files from the index.
|
||||||
@ -338,7 +316,6 @@ func (r *SharedRepo) RemoveFilesFromIndex(
|
|||||||
filenames ...string,
|
filenames ...string,
|
||||||
) error {
|
) error {
|
||||||
stdOut := new(bytes.Buffer)
|
stdOut := new(bytes.Buffer)
|
||||||
stdErr := new(bytes.Buffer)
|
|
||||||
stdIn := new(bytes.Buffer)
|
stdIn := new(bytes.Buffer)
|
||||||
for _, file := range filenames {
|
for _, file := range filenames {
|
||||||
if file != "" {
|
if file != "" {
|
||||||
@ -348,15 +325,19 @@ func (r *SharedRepo) RemoveFilesFromIndex(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gitea.NewCommand(ctx, "update-index", "--remove", "-z", "--index-info").
|
cmd := command.New("update-index",
|
||||||
Run(&gitea.RunOpts{
|
command.WithFlag("--remove"),
|
||||||
Dir: r.tmpPath,
|
command.WithFlag("-z"),
|
||||||
Stdin: stdIn,
|
command.WithFlag("--index-info"),
|
||||||
Stdout: stdOut,
|
)
|
||||||
Stderr: stdErr,
|
|
||||||
}); err != nil {
|
if err := cmd.Run(ctx,
|
||||||
return fmt.Errorf("unable to update-index for temporary repo: %s Error: %w\nstdout: %s\nstderr: %s",
|
command.WithDir(r.RepoPath),
|
||||||
r.repoUID, err, stdOut.String(), stdErr.String())
|
command.WithStdin(stdIn),
|
||||||
|
command.WithStdout(stdOut),
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("unable to update-index for temporary repo: %s Error: %w\nstdout: %s",
|
||||||
|
r.repoUID, err, stdOut.String())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -365,22 +346,22 @@ func (r *SharedRepo) RemoveFilesFromIndex(
|
|||||||
func (r *SharedRepo) WriteGitObject(
|
func (r *SharedRepo) WriteGitObject(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
content io.Reader,
|
content io.Reader,
|
||||||
) (string, error) {
|
) (sha.SHA, error) {
|
||||||
stdOut := new(bytes.Buffer)
|
stdOut := new(bytes.Buffer)
|
||||||
stdErr := new(bytes.Buffer)
|
cmd := command.New("hash-object",
|
||||||
|
command.WithFlag("-w"),
|
||||||
if err := gitea.NewCommand(ctx, "hash-object", "-w", "--stdin").
|
command.WithFlag("--stdin"),
|
||||||
Run(&gitea.RunOpts{
|
)
|
||||||
Dir: r.tmpPath,
|
if err := cmd.Run(ctx,
|
||||||
Stdin: content,
|
command.WithDir(r.RepoPath),
|
||||||
Stdout: stdOut,
|
command.WithStdin(content),
|
||||||
Stderr: stdErr,
|
command.WithStdout(stdOut),
|
||||||
}); err != nil {
|
); err != nil {
|
||||||
return "", fmt.Errorf("unable to hash-object to temporary repo: %s Error: %w\nstdout: %s\nstderr: %s",
|
return sha.None, fmt.Errorf("unable to hash-object to temporary repo: %s Error: %w\nstdout: %s",
|
||||||
r.repoUID, err, stdOut.String(), stdErr.String())
|
r.repoUID, err, stdOut.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.TrimSpace(stdOut.String()), nil
|
return sha.New(stdOut.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShowFile dumps show file and write to io.Writer.
|
// ShowFile dumps show file and write to io.Writer.
|
||||||
@ -390,15 +371,15 @@ func (r *SharedRepo) ShowFile(
|
|||||||
commitHash string,
|
commitHash string,
|
||||||
writer io.Writer,
|
writer io.Writer,
|
||||||
) error {
|
) error {
|
||||||
stderr := new(bytes.Buffer)
|
|
||||||
file := strings.TrimSpace(commitHash) + ":" + strings.TrimSpace(filePath)
|
file := strings.TrimSpace(commitHash) + ":" + strings.TrimSpace(filePath)
|
||||||
cmd := gitea.NewCommand(ctx, "show", file)
|
cmd := command.New("show",
|
||||||
if err := cmd.Run(&gitea.RunOpts{
|
command.WithArg(file),
|
||||||
Dir: r.repo.Path,
|
)
|
||||||
Stdout: writer,
|
if err := cmd.Run(ctx,
|
||||||
Stderr: stderr,
|
command.WithDir(r.RepoPath),
|
||||||
}); err != nil {
|
command.WithStdout(writer),
|
||||||
return fmt.Errorf("show file: %w - %s", err, stderr)
|
); err != nil {
|
||||||
|
return fmt.Errorf("show file: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -410,8 +391,13 @@ func (r *SharedRepo) AddObjectToIndex(
|
|||||||
objectHash string,
|
objectHash string,
|
||||||
objectPath string,
|
objectPath string,
|
||||||
) error {
|
) error {
|
||||||
if _, _, err := gitea.NewCommand(ctx, "update-index", "--add", "--replace", "--cacheinfo", mode, objectHash,
|
cmd := command.New("update-index",
|
||||||
objectPath).RunStdString(&gitea.RunOpts{Dir: r.tmpPath}); err != nil {
|
command.WithFlag("--add"),
|
||||||
|
command.WithFlag("--replace"),
|
||||||
|
command.WithFlag("--cacheinfo"),
|
||||||
|
command.WithArg(mode, objectHash, objectPath),
|
||||||
|
)
|
||||||
|
if err := cmd.Run(ctx, command.WithDir(r.RepoPath)); err != nil {
|
||||||
if matched, _ := regexp.MatchString(".*Invalid path '.*", err.Error()); matched {
|
if matched, _ := regexp.MatchString(".*Invalid path '.*", err.Error()); matched {
|
||||||
return errors.InvalidArgument("invalid path '%s'", objectPath)
|
return errors.InvalidArgument("invalid path '%s'", objectPath)
|
||||||
}
|
}
|
||||||
@ -422,13 +408,18 @@ func (r *SharedRepo) AddObjectToIndex(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WriteTree writes the current index as a tree to the object db and returns its hash.
|
// WriteTree writes the current index as a tree to the object db and returns its hash.
|
||||||
func (r *SharedRepo) WriteTree(ctx context.Context) (string, error) {
|
func (r *SharedRepo) WriteTree(ctx context.Context) (sha.SHA, error) {
|
||||||
stdout, _, err := gitea.NewCommand(ctx, "write-tree").RunStdString(&gitea.RunOpts{Dir: r.tmpPath})
|
stdout := &bytes.Buffer{}
|
||||||
|
cmd := command.New("write-tree")
|
||||||
|
err := cmd.Run(ctx,
|
||||||
|
command.WithDir(r.RepoPath),
|
||||||
|
command.WithStdout(stdout),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("unable to write-tree in temporary repo path for: %s Error: %w",
|
return sha.None, fmt.Errorf("unable to write-tree in temporary repo path for: %s Error: %w",
|
||||||
r.repoUID, err)
|
r.repoUID, err)
|
||||||
}
|
}
|
||||||
return strings.TrimSpace(stdout), nil
|
return sha.New(stdout.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLastCommit gets the last commit ID SHA of the repo.
|
// GetLastCommit gets the last commit ID SHA of the repo.
|
||||||
@ -444,73 +435,79 @@ func (r *SharedRepo) GetLastCommitByRef(
|
|||||||
if ref == "" {
|
if ref == "" {
|
||||||
ref = "HEAD"
|
ref = "HEAD"
|
||||||
}
|
}
|
||||||
stdout, _, err := gitea.NewCommand(ctx, "rev-parse", ref).RunStdString(&gitea.RunOpts{Dir: r.tmpPath})
|
stdout := &bytes.Buffer{}
|
||||||
|
cmd := command.New("rev-parse",
|
||||||
|
command.WithArg(ref),
|
||||||
|
)
|
||||||
|
err := cmd.Run(ctx,
|
||||||
|
command.WithDir(r.RepoPath),
|
||||||
|
command.WithStdout(stdout),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", processGiteaErrorf(err, "unable to rev-parse %s in temporary repo for: %s",
|
return "", processGitErrorf(err, "unable to rev-parse %s in temporary repo for: %s",
|
||||||
ref, r.repoUID)
|
ref, r.repoUID)
|
||||||
}
|
}
|
||||||
return strings.TrimSpace(stdout), nil
|
return strings.TrimSpace(stdout.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitTreeWithDate creates a commit from a given tree for the user with provided message.
|
// CommitTreeWithDate creates a commit from a given tree for the user with provided message.
|
||||||
func (r *SharedRepo) CommitTreeWithDate(
|
func (r *SharedRepo) CommitTreeWithDate(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
parent string,
|
parent sha.SHA,
|
||||||
author, committer *types.Identity,
|
author, committer *Identity,
|
||||||
treeHash, message string,
|
treeHash sha.SHA,
|
||||||
|
message string,
|
||||||
signoff bool,
|
signoff bool,
|
||||||
authorDate, committerDate time.Time,
|
authorDate, committerDate time.Time,
|
||||||
) (string, error) {
|
) (sha.SHA, error) {
|
||||||
// setup environment variables used by git-commit-tree
|
|
||||||
// See https://git-scm.com/book/en/v2/Git-Internals-Environment-Variables
|
|
||||||
env := []string{
|
|
||||||
"GIT_AUTHOR_NAME=" + author.Name,
|
|
||||||
"GIT_AUTHOR_EMAIL=" + author.Email,
|
|
||||||
"GIT_AUTHOR_DATE=" + authorDate.Format(time.RFC3339),
|
|
||||||
"GIT_COMMITTER_NAME=" + committer.Name,
|
|
||||||
"GIT_COMMITTER_EMAIL=" + committer.Email,
|
|
||||||
"GIT_COMMITTER_DATE=" + committerDate.Format(time.RFC3339),
|
|
||||||
}
|
|
||||||
messageBytes := new(bytes.Buffer)
|
messageBytes := new(bytes.Buffer)
|
||||||
_, _ = messageBytes.WriteString(message)
|
_, _ = messageBytes.WriteString(message)
|
||||||
_, _ = messageBytes.WriteString("\n")
|
_, _ = messageBytes.WriteString("\n")
|
||||||
|
|
||||||
var args []string
|
cmd := command.New("commit-tree",
|
||||||
if parent != "" {
|
command.WithAuthorAndDate(
|
||||||
args = []string{"commit-tree", treeHash, "-p", parent}
|
author.Name,
|
||||||
} else {
|
author.Email,
|
||||||
args = []string{"commit-tree", treeHash}
|
authorDate,
|
||||||
|
),
|
||||||
|
command.WithCommitterAndDate(
|
||||||
|
committer.Name,
|
||||||
|
committer.Email,
|
||||||
|
committerDate,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if !parent.IsEmpty() {
|
||||||
|
cmd.Add(command.WithFlag("-p", parent.String()))
|
||||||
}
|
}
|
||||||
|
cmd.Add(command.WithArg(treeHash.String()))
|
||||||
|
|
||||||
// temporary no signing
|
// temporary no signing
|
||||||
args = append(args, "--no-gpg-sign")
|
cmd.Add(command.WithFlag("--no-gpg-sign"))
|
||||||
|
|
||||||
if signoff {
|
if signoff {
|
||||||
giteaSignature := &gitea.Signature{
|
sig := &Signature{
|
||||||
Name: committer.Name,
|
Identity: Identity{
|
||||||
Email: committer.Email,
|
Name: committer.Name,
|
||||||
When: committerDate,
|
Email: committer.Email,
|
||||||
|
},
|
||||||
|
When: committerDate,
|
||||||
}
|
}
|
||||||
// Signed-off-by
|
// Signed-off-by
|
||||||
_, _ = messageBytes.WriteString("\n")
|
_, _ = messageBytes.WriteString("\n")
|
||||||
_, _ = messageBytes.WriteString("Signed-off-by: ")
|
_, _ = messageBytes.WriteString("Signed-off-by: ")
|
||||||
_, _ = messageBytes.WriteString(giteaSignature.String())
|
_, _ = messageBytes.WriteString(sig.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout := new(bytes.Buffer)
|
stdout := new(bytes.Buffer)
|
||||||
stderr := new(bytes.Buffer)
|
if err := cmd.Run(ctx,
|
||||||
if err := gitea.NewCommand(ctx, args...).
|
command.WithDir(r.RepoPath),
|
||||||
Run(&gitea.RunOpts{
|
command.WithStdin(messageBytes),
|
||||||
Env: env,
|
command.WithStdout(stdout),
|
||||||
Dir: r.tmpPath,
|
); err != nil {
|
||||||
Stdin: messageBytes,
|
return sha.None, processGitErrorf(err, "unable to commit-tree in temporary repo: %s Error: %v\nStdout: %s",
|
||||||
Stdout: stdout,
|
r.repoUID, err, stdout)
|
||||||
Stderr: stderr,
|
|
||||||
}); err != nil {
|
|
||||||
return "", processGiteaErrorf(err, "unable to commit-tree in temporary repo: %s Error: %v\nStdout: %s\nStderr: %s",
|
|
||||||
r.repoUID, err, stdout, stderr)
|
|
||||||
}
|
}
|
||||||
return strings.TrimSpace(stdout.String()), nil
|
return sha.New(stdout.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SharedRepo) PushDeleteBranch(
|
func (r *SharedRepo) PushDeleteBranch(
|
||||||
@ -580,7 +577,7 @@ func (r *SharedRepo) push(
|
|||||||
env ...string,
|
env ...string,
|
||||||
) error {
|
) error {
|
||||||
// Because calls hooks we need to pass in the environment
|
// Because calls hooks we need to pass in the environment
|
||||||
if err := r.adapter.Push(ctx, r.tmpPath, types.PushOptions{
|
if err := r.git.Push(ctx, r.RepoPath, PushOptions{
|
||||||
Remote: r.remoteRepoPath,
|
Remote: r.remoteRepoPath,
|
||||||
Branch: sourceRef + ":" + destinationRef,
|
Branch: sourceRef + ":" + destinationRef,
|
||||||
Env: env,
|
Env: env,
|
||||||
@ -592,29 +589,19 @@ func (r *SharedRepo) push(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBranchCommit Gets the commit object of the given branch.
|
|
||||||
func (r *SharedRepo) GetBranchCommit(branch string) (*gitea.Commit, error) {
|
|
||||||
if r.repo == nil {
|
|
||||||
return nil, fmt.Errorf("repository has not been cloned")
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.repo.GetBranchCommit(strings.TrimPrefix(branch, gitReferenceNamePrefixBranch))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBranch gets the branch object of the given ref.
|
// GetBranch gets the branch object of the given ref.
|
||||||
func (r *SharedRepo) GetBranch(rev string) (*gitea.Branch, error) {
|
func (r *SharedRepo) GetBranch(ctx context.Context, rev string) (*Branch, error) {
|
||||||
if r.repo == nil {
|
return r.git.GetBranch(ctx, r.RepoPath, rev)
|
||||||
return nil, fmt.Errorf("repository has not been cloned")
|
|
||||||
}
|
|
||||||
return r.repo.GetBranch(rev)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommit Gets the commit object of the given commit ID.
|
// GetCommit Gets the commit object of the given commit ID.
|
||||||
func (r *SharedRepo) GetCommit(commitID string) (*gitea.Commit, error) {
|
func (r *SharedRepo) GetCommit(ctx context.Context, commitID string) (*Commit, error) {
|
||||||
if r.repo == nil {
|
return r.git.GetCommit(ctx, r.RepoPath, commitID)
|
||||||
return nil, fmt.Errorf("repository has not been cloned")
|
}
|
||||||
}
|
|
||||||
return r.repo.GetCommit(commitID)
|
// GetTreeNode Gets the tree node object of the given commit ID and path.
|
||||||
|
func (r *SharedRepo) GetTreeNode(ctx context.Context, commitID, treePath string) (*TreeNode, error) {
|
||||||
|
return r.git.GetTreeNode(ctx, r.RepoPath, commitID, treePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetReferenceFromBranchName assumes the provided value is the branch name (not the ref!)
|
// GetReferenceFromBranchName assumes the provided value is the branch name (not the ref!)
|
||||||
@ -643,12 +630,3 @@ func GetReferenceFromTagName(tagName string) string {
|
|||||||
// return reference
|
// return reference
|
||||||
return gitReferenceNamePrefixTag + tagName
|
return gitReferenceNamePrefixTag + tagName
|
||||||
}
|
}
|
||||||
|
|
||||||
// SharedRepository creates new instance of SharedRepo.
|
|
||||||
func (a Adapter) SharedRepository(
|
|
||||||
tmpDir string,
|
|
||||||
repoUID string,
|
|
||||||
remotePath string,
|
|
||||||
) (*SharedRepo, error) {
|
|
||||||
return NewSharedRepo(a, tmpDir, repoUID, remotePath)
|
|
||||||
}
|
|
63
git/api/signature.go
Normal file
63
git/api/signature.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2023 Harness, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Signature represents the Author or Committer information.
|
||||||
|
type Signature struct {
|
||||||
|
Identity Identity
|
||||||
|
// When is the timestamp of the Signature.
|
||||||
|
When time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeSignature decodes a byte array representing a signature to signature.
|
||||||
|
func DecodeSignature(b []byte) (Signature, error) {
|
||||||
|
sig, err := NewSignatureFromCommitLine(b)
|
||||||
|
if err != nil {
|
||||||
|
return Signature{}, fmt.Errorf("failed to read signature from commit: %w", err)
|
||||||
|
}
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Signature) String() string {
|
||||||
|
return fmt.Sprintf("%s <%s>", s.Identity.Name, s.Identity.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Identity struct {
|
||||||
|
Name string
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Identity) String() string {
|
||||||
|
return fmt.Sprintf("%s <%s>", i.Name, i.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Identity) Validate() error {
|
||||||
|
if i.Name == "" {
|
||||||
|
return errors.InvalidArgument("identity name is mandatory")
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Email == "" {
|
||||||
|
return errors.InvalidArgument("identity email is mandatory")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
91
git/api/submodule.go
Normal file
91
git/api/submodule.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2023 Harness, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Submodule struct {
|
||||||
|
Name string
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubmodule returns the submodule at the given path reachable from ref.
|
||||||
|
// Note: ref can be Branch / Tag / CommitSHA.
|
||||||
|
func (g *Git) GetSubmodule(
|
||||||
|
ctx context.Context,
|
||||||
|
repoPath string,
|
||||||
|
ref string,
|
||||||
|
treePath string,
|
||||||
|
) (*Submodule, error) {
|
||||||
|
if repoPath == "" {
|
||||||
|
return nil, ErrRepositoryPathEmpty
|
||||||
|
}
|
||||||
|
treePath = cleanTreePath(treePath)
|
||||||
|
|
||||||
|
// Get the commit object for the ref
|
||||||
|
commit, err := g.GetFullCommitID(ctx, repoPath, ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, processGitErrorf(err, "error getting commit for ref '%s'", ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
node, err := g.GetTreeNode(ctx, repoPath, commit.String(), ".gitmodules")
|
||||||
|
if err != nil {
|
||||||
|
return nil, processGitErrorf(err, "error reading tree node for ref '%s' with commit '%s'",
|
||||||
|
ref, commit)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := g.GetBlob(ctx, repoPath, node.SHA, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, processGitErrorf(err, "error reading commit for ref '%s'", ref)
|
||||||
|
}
|
||||||
|
defer reader.Content.Close()
|
||||||
|
|
||||||
|
modules, err := GetSubModules(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, processGitErrorf(err, "error getting submodule '%s' from commit", treePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return modules[treePath], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubModules get all the sub modules of current revision git tree.
|
||||||
|
func GetSubModules(rd *BlobReader) (map[string]*Submodule, error) {
|
||||||
|
var isModule bool
|
||||||
|
var path string
|
||||||
|
submodules := make(map[string]*Submodule, 4)
|
||||||
|
scanner := bufio.NewScanner(rd.Content)
|
||||||
|
for scanner.Scan() {
|
||||||
|
if strings.HasPrefix(scanner.Text(), "[submodule") {
|
||||||
|
isModule = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isModule {
|
||||||
|
fields := strings.Split(scanner.Text(), "=")
|
||||||
|
k := strings.TrimSpace(fields[0])
|
||||||
|
if k == "path" {
|
||||||
|
path = strings.TrimSpace(fields[1])
|
||||||
|
} else if k == "url" {
|
||||||
|
submodules[path] = &Submodule{path, strings.TrimSpace(fields[1])}
|
||||||
|
isModule = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return submodules, nil
|
||||||
|
}
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -25,7 +25,7 @@ import (
|
|||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git/command"
|
"github.com/harness/gitness/git/command"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/sha"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -33,42 +33,65 @@ const (
|
|||||||
pgpSignatureEndToken = "\n-----END PGP SIGNATURE-----" //#nosec G101
|
pgpSignatureEndToken = "\n-----END PGP SIGNATURE-----" //#nosec G101
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Tag struct {
|
||||||
|
Sha sha.SHA
|
||||||
|
Name string
|
||||||
|
TargetSha sha.SHA
|
||||||
|
TargetType GitObjectType
|
||||||
|
Title string
|
||||||
|
Message string
|
||||||
|
Tagger Signature
|
||||||
|
Signature *CommitGPGSignature
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateTagOptions struct {
|
||||||
|
// Message is the optional message the tag will be created with - if the message is empty
|
||||||
|
// the tag will be lightweight, otherwise it'll be annotated.
|
||||||
|
Message string
|
||||||
|
|
||||||
|
// Tagger is the information used in case the tag is annotated (Message is provided).
|
||||||
|
Tagger Signature
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagPrefix tags prefix path on the repository.
|
||||||
|
const TagPrefix = "refs/tags/"
|
||||||
|
|
||||||
// GetAnnotatedTag returns the tag for a specific tag sha.
|
// GetAnnotatedTag returns the tag for a specific tag sha.
|
||||||
func (a Adapter) GetAnnotatedTag(
|
func (g *Git) GetAnnotatedTag(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
sha string,
|
rev string,
|
||||||
) (*types.Tag, error) {
|
) (*Tag, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return nil, ErrRepositoryPathEmpty
|
return nil, ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
tags, err := getAnnotatedTags(ctx, repoPath, []string{sha})
|
tags, err := getAnnotatedTags(ctx, repoPath, []string{rev})
|
||||||
if err != nil || len(tags) == 0 {
|
if err != nil || len(tags) == 0 {
|
||||||
return nil, processGiteaErrorf(err, "failed to get annotated tag with sha '%s'", sha)
|
return nil, processGitErrorf(err, "failed to get annotated tag with sha '%s'", rev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &tags[0], nil
|
return &tags[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAnnotatedTags returns the tags for a specific list of tag sha.
|
// GetAnnotatedTags returns the tags for a specific list of tag sha.
|
||||||
func (a Adapter) GetAnnotatedTags(
|
func (g *Git) GetAnnotatedTags(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
shas []string,
|
revs []string,
|
||||||
) ([]types.Tag, error) {
|
) ([]Tag, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return nil, ErrRepositoryPathEmpty
|
return nil, ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
return getAnnotatedTags(ctx, repoPath, shas)
|
return getAnnotatedTags(ctx, repoPath, revs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateTag creates the tag pointing at the provided SHA (could be any type, e.g. commit, tag, blob, ...)
|
// CreateTag creates the tag pointing at the provided SHA (could be any type, e.g. commit, tag, blob, ...)
|
||||||
func (a Adapter) CreateTag(
|
func (g *Git) CreateTag(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
name string,
|
name string,
|
||||||
targetSHA string,
|
targetSHA sha.SHA,
|
||||||
opts *types.CreateTagOptions,
|
opts *CreateTagOptions,
|
||||||
) error {
|
) error {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return ErrRepositoryPathEmpty
|
return ErrRepositoryPathEmpty
|
||||||
@ -85,21 +108,20 @@ func (a Adapter) CreateTag(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Add(command.WithArg(name, targetSHA))
|
cmd.Add(command.WithArg(name, targetSHA.String()))
|
||||||
err := cmd.Run(ctx, command.WithDir(repoPath))
|
err := cmd.Run(ctx, command.WithDir(repoPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return processGiteaErrorf(err, "Service failed to create a tag")
|
return processGitErrorf(err, "Service failed to create a tag")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAnnotatedTag is a custom implementation to retrieve an annotated tag from a sha.
|
// getAnnotatedTag is a custom implementation to retrieve an annotated tag from a sha.
|
||||||
// The code is following parts of the gitea implementation.
|
|
||||||
func getAnnotatedTags(
|
func getAnnotatedTags(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
shas []string,
|
revs []string,
|
||||||
) ([]types.Tag, error) {
|
) ([]Tag, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return nil, ErrRepositoryPathEmpty
|
return nil, ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
@ -110,25 +132,27 @@ func getAnnotatedTags(
|
|||||||
_ = writer.Close()
|
_ = writer.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
tags := make([]types.Tag, len(shas))
|
tags := make([]Tag, len(revs))
|
||||||
|
|
||||||
for i, sha := range shas {
|
for i, rev := range revs {
|
||||||
if _, err := writer.Write([]byte(sha + "\n")); err != nil {
|
line := rev + "\n"
|
||||||
|
if _, err := writer.Write([]byte(line)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tagSha, typ, size, err := ReadBatchHeaderLine(reader)
|
output, err := ReadBatchHeaderLine(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, io.EOF) || errors.IsNotFound(err) {
|
if errors.Is(err, io.EOF) || errors.IsNotFound(err) {
|
||||||
return nil, fmt.Errorf("tag with sha %s does not exist", sha)
|
return nil, fmt.Errorf("tag with sha %s does not exist", rev)
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if typ != string(types.GitObjectTypeTag) {
|
if output.Type != string(GitObjectTypeTag) {
|
||||||
return nil, fmt.Errorf("git object is of type '%s', expected tag", typ)
|
return nil, fmt.Errorf("git object is of type '%s', expected tag",
|
||||||
|
output.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
// read the remaining rawData
|
// read the remaining rawData
|
||||||
rawData, err := io.ReadAll(io.LimitReader(reader, size))
|
rawData, err := io.ReadAll(io.LimitReader(reader, output.Size))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -139,11 +163,11 @@ func getAnnotatedTags(
|
|||||||
|
|
||||||
tag, err := parseTagDataFromCatFile(rawData)
|
tag, err := parseTagDataFromCatFile(rawData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse tag '%s': %w", sha, err)
|
return nil, fmt.Errorf("failed to parse tag '%s': %w", rev, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill in the sha
|
// fill in the sha
|
||||||
tag.Sha = string(tagSha)
|
tag.Sha = output.SHA
|
||||||
|
|
||||||
tags[i] = tag
|
tags[i] = tag
|
||||||
}
|
}
|
||||||
@ -152,14 +176,13 @@ func getAnnotatedTags(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseTagDataFromCatFile parses a tag from a cat-file output.
|
// parseTagDataFromCatFile parses a tag from a cat-file output.
|
||||||
func parseTagDataFromCatFile(data []byte) (tag types.Tag, err error) {
|
func parseTagDataFromCatFile(data []byte) (tag Tag, err error) {
|
||||||
p := 0
|
|
||||||
|
|
||||||
// parse object Id
|
// parse object Id
|
||||||
tag.TargetSha, p, err = parseCatFileLine(data, p, "object")
|
object, p, err := parseCatFileLine(data, 0, "object")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tag, err
|
return tag, err
|
||||||
}
|
}
|
||||||
|
tag.TargetSha = sha.Must(object)
|
||||||
|
|
||||||
// parse object type
|
// parse object type
|
||||||
rawType, p, err := parseCatFileLine(data, p, "type")
|
rawType, p, err := parseCatFileLine(data, p, "type")
|
||||||
@ -167,7 +190,7 @@ func parseTagDataFromCatFile(data []byte) (tag types.Tag, err error) {
|
|||||||
return tag, err
|
return tag, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tag.TargetType, err = types.ParseGitObjectType(rawType)
|
tag.TargetType, err = ParseGitObjectType(rawType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tag, err
|
return tag, err
|
||||||
}
|
}
|
||||||
@ -247,19 +270,18 @@ const defaultGitTimeLayout = "Mon Jan _2 15:04:05 2006 -0700"
|
|||||||
// parseSignatureFromCatFileLine parses the signature from a cat-file output.
|
// parseSignatureFromCatFileLine parses the signature from a cat-file output.
|
||||||
// This is used for commit / tag outputs. Input will be similar to (without 'author 'prefix):
|
// This is used for commit / tag outputs. Input will be similar to (without 'author 'prefix):
|
||||||
// - author Max Mustermann <mm@gitness.io> 1666401234 -0700
|
// - author Max Mustermann <mm@gitness.io> 1666401234 -0700
|
||||||
// - author Max Mustermann <mm@gitness.io> Tue Oct 18 05:13:26 2022 +0530
|
// - author Max Mustermann <mm@gitness.io> Tue Oct 18 05:13:26 2022 +0530.
|
||||||
// TODO: method is leaning on gitea code - requires reference?
|
func parseSignatureFromCatFileLine(line string) (Signature, error) {
|
||||||
func parseSignatureFromCatFileLine(line string) (types.Signature, error) {
|
sig := Signature{}
|
||||||
sig := types.Signature{}
|
|
||||||
emailStart := strings.LastIndexByte(line, '<')
|
emailStart := strings.LastIndexByte(line, '<')
|
||||||
emailEnd := strings.LastIndexByte(line, '>')
|
emailEnd := strings.LastIndexByte(line, '>')
|
||||||
if emailStart == -1 || emailEnd == -1 || emailEnd < emailStart {
|
if emailStart == -1 || emailEnd == -1 || emailEnd < emailStart {
|
||||||
return types.Signature{}, fmt.Errorf("signature is missing email ('%s')", line)
|
return Signature{}, fmt.Errorf("signature is missing email ('%s')", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
// name requires that there is at least one char followed by a space (so emailStart >= 2)
|
// name requires that there is at least one char followed by a space (so emailStart >= 2)
|
||||||
if emailStart < 2 {
|
if emailStart < 2 {
|
||||||
return types.Signature{}, fmt.Errorf("signature is missing name ('%s')", line)
|
return Signature{}, fmt.Errorf("signature is missing name ('%s')", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
sig.Identity.Name = line[:emailStart-1]
|
sig.Identity.Name = line[:emailStart-1]
|
||||||
@ -267,7 +289,7 @@ func parseSignatureFromCatFileLine(line string) (types.Signature, error) {
|
|||||||
|
|
||||||
timeStart := emailEnd + 2
|
timeStart := emailEnd + 2
|
||||||
if timeStart >= len(line) {
|
if timeStart >= len(line) {
|
||||||
return types.Signature{}, fmt.Errorf("signature is missing time ('%s')", line)
|
return Signature{}, fmt.Errorf("signature is missing time ('%s')", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if time format is written date time format (e.g Thu, 07 Apr 2005 22:13:13 +0200)
|
// Check if time format is written date time format (e.g Thu, 07 Apr 2005 22:13:13 +0200)
|
||||||
@ -276,7 +298,7 @@ func parseSignatureFromCatFileLine(line string) (types.Signature, error) {
|
|||||||
var err error
|
var err error
|
||||||
sig.When, err = time.Parse(defaultGitTimeLayout, line[timeStart:])
|
sig.When, err = time.Parse(defaultGitTimeLayout, line[timeStart:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.Signature{}, fmt.Errorf("failed to time.parse signature time ('%s'): %w", line, err)
|
return Signature{}, fmt.Errorf("failed to time.parse signature time ('%s'): %w", line, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sig, nil
|
return sig, nil
|
||||||
@ -285,19 +307,19 @@ func parseSignatureFromCatFileLine(line string) (types.Signature, error) {
|
|||||||
// Otherwise we have to manually parse unix time and time zone
|
// Otherwise we have to manually parse unix time and time zone
|
||||||
endOfUnixTime := timeStart + strings.IndexByte(line[timeStart:], ' ')
|
endOfUnixTime := timeStart + strings.IndexByte(line[timeStart:], ' ')
|
||||||
if endOfUnixTime <= timeStart {
|
if endOfUnixTime <= timeStart {
|
||||||
return types.Signature{}, fmt.Errorf("signature is missing unix time ('%s')", line)
|
return Signature{}, fmt.Errorf("signature is missing unix time ('%s')", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
unixSeconds, err := strconv.ParseInt(line[timeStart:endOfUnixTime], 10, 64)
|
unixSeconds, err := strconv.ParseInt(line[timeStart:endOfUnixTime], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.Signature{}, fmt.Errorf("failed to parse unix time ('%s'): %w", line, err)
|
return Signature{}, fmt.Errorf("failed to parse unix time ('%s'): %w", line, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse time zone
|
// parse time zone
|
||||||
startOfTimeZone := endOfUnixTime + 1 // +1 for space
|
startOfTimeZone := endOfUnixTime + 1 // +1 for space
|
||||||
endOfTimeZone := startOfTimeZone + 5 // +5 for '+0700'
|
endOfTimeZone := startOfTimeZone + 5 // +5 for '+0700'
|
||||||
if startOfTimeZone >= len(line) || endOfTimeZone > len(line) {
|
if startOfTimeZone >= len(line) || endOfTimeZone > len(line) {
|
||||||
return types.Signature{}, fmt.Errorf("signature is missing time zone ('%s')", line)
|
return Signature{}, fmt.Errorf("signature is missing time zone ('%s')", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get and disect timezone, e.g. '+0700'
|
// get and disect timezone, e.g. '+0700'
|
||||||
@ -306,11 +328,11 @@ func parseSignatureFromCatFileLine(line string) (types.Signature, error) {
|
|||||||
rawTimeZoneMin := rawTimeZone[3:] // gets +07[00]
|
rawTimeZoneMin := rawTimeZone[3:] // gets +07[00]
|
||||||
timeZoneH, err := strconv.ParseInt(rawTimeZoneH, 10, 64)
|
timeZoneH, err := strconv.ParseInt(rawTimeZoneH, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.Signature{}, fmt.Errorf("failed to parse hours of time zone ('%s'): %w", line, err)
|
return Signature{}, fmt.Errorf("failed to parse hours of time zone ('%s'): %w", line, err)
|
||||||
}
|
}
|
||||||
timeZoneMin, err := strconv.ParseInt(rawTimeZoneMin, 10, 64)
|
timeZoneMin, err := strconv.ParseInt(rawTimeZoneMin, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.Signature{}, fmt.Errorf("failed to parse minutes of time zone ('%s'): %w", line, err)
|
return Signature{}, fmt.Errorf("failed to parse minutes of time zone ('%s'): %w", line, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
timeZoneOffsetInSec := int(timeZoneH*60+timeZoneMin) * 60
|
timeZoneOffsetInSec := int(timeZoneH*60+timeZoneMin) * 60
|
||||||
@ -324,3 +346,55 @@ func parseSignatureFromCatFileLine(line string) (types.Signature, error) {
|
|||||||
|
|
||||||
return sig, nil
|
return sig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse commit information from the (uncompressed) raw
|
||||||
|
// data from the commit object.
|
||||||
|
// \n\n separate headers from message.
|
||||||
|
func parseTagData(data []byte) (*Tag, error) {
|
||||||
|
tag := &Tag{
|
||||||
|
Tagger: Signature{},
|
||||||
|
}
|
||||||
|
// we now have the contents of the commit object. Let's investigate...
|
||||||
|
nextLine := 0
|
||||||
|
l:
|
||||||
|
for {
|
||||||
|
eol := bytes.IndexByte(data[nextLine:], '\n')
|
||||||
|
switch {
|
||||||
|
case eol > 0:
|
||||||
|
line := data[nextLine : nextLine+eol]
|
||||||
|
spacePos := bytes.IndexByte(line, ' ')
|
||||||
|
refType := line[:spacePos]
|
||||||
|
switch string(refType) {
|
||||||
|
case "object":
|
||||||
|
tag.TargetSha = sha.Must(string(line[spacePos+1:]))
|
||||||
|
case "type":
|
||||||
|
// A commit can have one or more parents
|
||||||
|
tag.TargetType = GitObjectType(line[spacePos+1:])
|
||||||
|
case "tagger":
|
||||||
|
sig, err := NewSignatureFromCommitLine(line[spacePos+1:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse tagger signature: %w", err)
|
||||||
|
}
|
||||||
|
tag.Tagger = sig
|
||||||
|
}
|
||||||
|
nextLine += eol + 1
|
||||||
|
case eol == 0:
|
||||||
|
tag.Message = string(data[nextLine+1:])
|
||||||
|
break l
|
||||||
|
default:
|
||||||
|
break l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
idx := strings.LastIndex(tag.Message, pgpSignatureBeginToken)
|
||||||
|
if idx > 0 {
|
||||||
|
endSigIdx := strings.Index(tag.Message[idx:], pgpSignatureEndToken)
|
||||||
|
if endSigIdx > 0 {
|
||||||
|
tag.Signature = &CommitGPGSignature{
|
||||||
|
Signature: tag.Message[idx+1 : idx+endSigIdx+len(pgpSignatureEndToken)],
|
||||||
|
Payload: string(data[:bytes.LastIndex(data, []byte(pgpSignatureBeginToken))+1]),
|
||||||
|
}
|
||||||
|
tag.Message = tag.Message[:idx+1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tag, nil
|
||||||
|
}
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@ -22,34 +22,99 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git/command"
|
"github.com/harness/gitness/git/command"
|
||||||
"github.com/harness/gitness/git/parser"
|
"github.com/harness/gitness/git/parser"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/sha"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TreeNodeWithCommit struct {
|
||||||
|
TreeNode
|
||||||
|
Commit *Commit
|
||||||
|
}
|
||||||
|
|
||||||
|
type TreeNode struct {
|
||||||
|
NodeType TreeNodeType
|
||||||
|
Mode TreeNodeMode
|
||||||
|
SHA sha.SHA
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *TreeNode) IsExecutable() bool {
|
||||||
|
return n.Mode == TreeNodeModeExec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *TreeNode) IsDir() bool {
|
||||||
|
return n.Mode == TreeNodeModeTree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *TreeNode) IsLink() bool {
|
||||||
|
return n.Mode == TreeNodeModeSymlink
|
||||||
|
}
|
||||||
|
|
||||||
|
// TreeNodeType specifies the different types of nodes in a git tree.
|
||||||
|
// IMPORTANT: has to be consistent with rpc.TreeNodeType (proto).
|
||||||
|
type TreeNodeType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TreeNodeTypeTree TreeNodeType = iota
|
||||||
|
TreeNodeTypeBlob
|
||||||
|
TreeNodeTypeCommit
|
||||||
|
)
|
||||||
|
|
||||||
|
// TreeNodeMode specifies the different modes of a node in a git tree.
|
||||||
|
// IMPORTANT: has to be consistent with rpc.TreeNodeMode (proto).
|
||||||
|
type TreeNodeMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TreeNodeModeFile TreeNodeMode = iota
|
||||||
|
TreeNodeModeSymlink
|
||||||
|
TreeNodeModeExec
|
||||||
|
TreeNodeModeTree
|
||||||
|
TreeNodeModeCommit
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m TreeNodeMode) String() string {
|
||||||
|
var result int
|
||||||
|
switch m {
|
||||||
|
case TreeNodeModeFile:
|
||||||
|
result = 0o100644
|
||||||
|
case TreeNodeModeSymlink:
|
||||||
|
result = 0o120000
|
||||||
|
case TreeNodeModeExec:
|
||||||
|
result = 0o100755
|
||||||
|
case TreeNodeModeTree:
|
||||||
|
result = 0o040000
|
||||||
|
case TreeNodeModeCommit:
|
||||||
|
result = 0o160000
|
||||||
|
}
|
||||||
|
return strconv.FormatInt(int64(result), 8)
|
||||||
|
}
|
||||||
|
|
||||||
func cleanTreePath(treePath string) string {
|
func cleanTreePath(treePath string) string {
|
||||||
return strings.Trim(path.Clean("/"+treePath), "/")
|
return strings.Trim(path.Clean("/"+treePath), "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTreeNodeMode(s string) (types.TreeNodeType, types.TreeNodeMode, error) {
|
func parseTreeNodeMode(s string) (TreeNodeType, TreeNodeMode, error) {
|
||||||
switch s {
|
switch s {
|
||||||
case "100644":
|
case "100644":
|
||||||
return types.TreeNodeTypeBlob, types.TreeNodeModeFile, nil
|
return TreeNodeTypeBlob, TreeNodeModeFile, nil
|
||||||
case "120000":
|
case "120000":
|
||||||
return types.TreeNodeTypeBlob, types.TreeNodeModeSymlink, nil
|
return TreeNodeTypeBlob, TreeNodeModeSymlink, nil
|
||||||
case "100755":
|
case "100755":
|
||||||
return types.TreeNodeTypeBlob, types.TreeNodeModeExec, nil
|
return TreeNodeTypeBlob, TreeNodeModeExec, nil
|
||||||
case "160000":
|
case "160000":
|
||||||
return types.TreeNodeTypeCommit, types.TreeNodeModeCommit, nil
|
return TreeNodeTypeCommit, TreeNodeModeCommit, nil
|
||||||
case "040000":
|
case "040000":
|
||||||
return types.TreeNodeTypeTree, types.TreeNodeModeTree, nil
|
return TreeNodeTypeTree, TreeNodeModeTree, nil
|
||||||
default:
|
default:
|
||||||
return types.TreeNodeTypeBlob, types.TreeNodeModeFile,
|
return TreeNodeTypeBlob, TreeNodeModeFile,
|
||||||
fmt.Errorf("unknown git tree node mode: '%s'", s)
|
fmt.Errorf("unknown git tree node mode: '%s'", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,7 +129,7 @@ func lsTree(
|
|||||||
repoPath string,
|
repoPath string,
|
||||||
rev string,
|
rev string,
|
||||||
treePath string,
|
treePath string,
|
||||||
) ([]types.TreeNode, error) {
|
) ([]TreeNode, error) {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
return nil, ErrRepositoryPathEmpty
|
return nil, ErrRepositoryPathEmpty
|
||||||
}
|
}
|
||||||
@ -86,12 +151,12 @@ func lsTree(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if output.Len() == 0 {
|
if output.Len() == 0 {
|
||||||
return nil, &types.PathNotFoundError{Path: treePath}
|
return nil, errors.NotFound("path '%s' wasn't found in the repo", treePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
n := bytes.Count(output.Bytes(), []byte{'\x00'})
|
n := bytes.Count(output.Bytes(), []byte{'\x00'})
|
||||||
|
|
||||||
list := make([]types.TreeNode, 0, n)
|
list := make([]TreeNode, 0, n)
|
||||||
scan := bufio.NewScanner(output)
|
scan := bufio.NewScanner(output)
|
||||||
scan.Split(parser.ScanZeroSeparated)
|
scan.Split(parser.ScanZeroSeparated)
|
||||||
for scan.Scan() {
|
for scan.Scan() {
|
||||||
@ -114,14 +179,14 @@ func lsTree(
|
|||||||
return nil, fmt.Errorf("failed to parse git node type and file mode: %w", err)
|
return nil, fmt.Errorf("failed to parse git node type and file mode: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeSha := columns[3]
|
nodeSha := sha.Must(columns[3])
|
||||||
nodePath := columns[4]
|
nodePath := columns[4]
|
||||||
nodeName := path.Base(nodePath)
|
nodeName := path.Base(nodePath)
|
||||||
|
|
||||||
list = append(list, types.TreeNode{
|
list = append(list, TreeNode{
|
||||||
NodeType: nodeType,
|
NodeType: nodeType,
|
||||||
Mode: nodeMode,
|
Mode: nodeMode,
|
||||||
Sha: nodeSha,
|
SHA: nodeSha,
|
||||||
Name: nodeName,
|
Name: nodeName,
|
||||||
Path: nodePath,
|
Path: nodePath,
|
||||||
})
|
})
|
||||||
@ -136,7 +201,7 @@ func lsDirectory(
|
|||||||
repoPath string,
|
repoPath string,
|
||||||
rev string,
|
rev string,
|
||||||
treePath string,
|
treePath string,
|
||||||
) ([]types.TreeNode, error) {
|
) ([]TreeNode, error) {
|
||||||
treePath = path.Clean(treePath)
|
treePath = path.Clean(treePath)
|
||||||
if treePath == "" {
|
if treePath == "" {
|
||||||
treePath = "."
|
treePath = "."
|
||||||
@ -153,22 +218,22 @@ func lsFile(
|
|||||||
repoPath string,
|
repoPath string,
|
||||||
rev string,
|
rev string,
|
||||||
treePath string,
|
treePath string,
|
||||||
) (types.TreeNode, error) {
|
) (TreeNode, error) {
|
||||||
treePath = cleanTreePath(treePath)
|
treePath = cleanTreePath(treePath)
|
||||||
|
|
||||||
list, err := lsTree(ctx, repoPath, rev, treePath)
|
list, err := lsTree(ctx, repoPath, rev, treePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.TreeNode{}, fmt.Errorf("failed to ls file: %w", err)
|
return TreeNode{}, fmt.Errorf("failed to ls file: %w", err)
|
||||||
}
|
}
|
||||||
if len(list) != 1 {
|
if len(list) != 1 {
|
||||||
return types.TreeNode{}, fmt.Errorf("ls file list contains more than one element, len=%d", len(list))
|
return TreeNode{}, fmt.Errorf("ls file list contains more than one element, len=%d", len(list))
|
||||||
}
|
}
|
||||||
|
|
||||||
return list[0], nil
|
return list[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTreeNode returns the tree node at the given path as found for the provided reference.
|
// GetTreeNode returns the tree node at the given path as found for the provided reference.
|
||||||
func (a Adapter) GetTreeNode(ctx context.Context, repoPath, rev, treePath string) (*types.TreeNode, error) {
|
func (g *Git) GetTreeNode(ctx context.Context, repoPath, rev, treePath string) (*TreeNode, error) {
|
||||||
// root path (empty path) is a special case
|
// root path (empty path) is a special case
|
||||||
if treePath == "" {
|
if treePath == "" {
|
||||||
if repoPath == "" {
|
if repoPath == "" {
|
||||||
@ -177,7 +242,7 @@ func (a Adapter) GetTreeNode(ctx context.Context, repoPath, rev, treePath string
|
|||||||
cmd := command.New("show",
|
cmd := command.New("show",
|
||||||
command.WithFlag("--no-patch"),
|
command.WithFlag("--no-patch"),
|
||||||
command.WithFlag("--format="+fmtTreeHash),
|
command.WithFlag("--format="+fmtTreeHash),
|
||||||
command.WithArg(rev),
|
command.WithArg(rev+"^{commit}"),
|
||||||
)
|
)
|
||||||
output := &bytes.Buffer{}
|
output := &bytes.Buffer{}
|
||||||
err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(output))
|
err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(output))
|
||||||
@ -188,10 +253,10 @@ func (a Adapter) GetTreeNode(ctx context.Context, repoPath, rev, treePath string
|
|||||||
return nil, fmt.Errorf("failed to get root tree node: %w", err)
|
return nil, fmt.Errorf("failed to get root tree node: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &types.TreeNode{
|
return &TreeNode{
|
||||||
NodeType: types.TreeNodeTypeTree,
|
NodeType: TreeNodeTypeTree,
|
||||||
Mode: types.TreeNodeModeTree,
|
Mode: TreeNodeModeTree,
|
||||||
Sha: strings.TrimSpace(output.String()),
|
SHA: sha.Must(output.String()),
|
||||||
Name: "",
|
Name: "",
|
||||||
Path: "",
|
Path: "",
|
||||||
}, err
|
}, err
|
||||||
@ -206,7 +271,7 @@ func (a Adapter) GetTreeNode(ctx context.Context, repoPath, rev, treePath string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListTreeNodes lists the child nodes of a tree reachable from ref via the specified path.
|
// ListTreeNodes lists the child nodes of a tree reachable from ref via the specified path.
|
||||||
func (a Adapter) ListTreeNodes(ctx context.Context, repoPath, rev, treePath string) ([]types.TreeNode, error) {
|
func (g *Git) ListTreeNodes(ctx context.Context, repoPath, rev, treePath string) ([]TreeNode, error) {
|
||||||
list, err := lsDirectory(ctx, repoPath, rev, treePath)
|
list, err := lsDirectory(ctx, repoPath, rev, treePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to list tree nodes: %w", err)
|
return nil, fmt.Errorf("failed to list tree nodes: %w", err)
|
||||||
@ -215,7 +280,7 @@ func (a Adapter) ListTreeNodes(ctx context.Context, repoPath, rev, treePath stri
|
|||||||
return list, nil
|
return list, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Adapter) ReadTree(
|
func (g *Git) ReadTree(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
ref string,
|
ref string,
|
139
git/api/util.go
Normal file
139
git/api/util.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
// Copyright 2023 Harness, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/yuin/goldmark/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// GitTimeLayout is the (default) time layout used by git.
|
||||||
|
GitTimeLayout = "Mon Jan _2 15:04:05 2006 -0700"
|
||||||
|
|
||||||
|
userPlaceholder = "sanitized-credential"
|
||||||
|
)
|
||||||
|
|
||||||
|
var schemeSep = []byte("://")
|
||||||
|
|
||||||
|
func NewSignatureFromCommitLine(line []byte) (Signature, error) {
|
||||||
|
emailStart := bytes.LastIndexByte(line, '<')
|
||||||
|
emailEnd := bytes.LastIndexByte(line, '>')
|
||||||
|
if emailStart == -1 || emailEnd == -1 || emailEnd < emailStart {
|
||||||
|
return Signature{}, ErrInvalidSignature
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := Signature{
|
||||||
|
Identity: Identity{
|
||||||
|
Name: string(line[:emailStart-1]),
|
||||||
|
Email: string(line[emailStart+1 : emailEnd]),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dateStart := emailEnd + 2
|
||||||
|
hasTime := dateStart < len(line)
|
||||||
|
if !hasTime {
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check date format.
|
||||||
|
firstChar := line[dateStart]
|
||||||
|
//nolint:nestif
|
||||||
|
if firstChar >= 48 && firstChar <= 57 {
|
||||||
|
idx := bytes.IndexByte(line[dateStart:], ' ')
|
||||||
|
if idx < 0 {
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
timestring := string(line[dateStart : dateStart+idx])
|
||||||
|
seconds, _ := strconv.ParseInt(timestring, 10, 64)
|
||||||
|
sig.When = time.Unix(seconds, 0)
|
||||||
|
|
||||||
|
idx += emailEnd + 3
|
||||||
|
if idx >= len(line) || idx+5 > len(line) {
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
timezone := string(line[idx : idx+5])
|
||||||
|
tzhours, err := strconv.ParseInt(timezone[0:3], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return Signature{}, fmt.Errorf("failed to parse tzhours: %w", err)
|
||||||
|
}
|
||||||
|
tzmins, err := strconv.ParseInt(timezone[3:], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return Signature{}, fmt.Errorf("failed to parse tzmins: %w", err)
|
||||||
|
}
|
||||||
|
if tzhours < 0 {
|
||||||
|
tzmins *= -1
|
||||||
|
}
|
||||||
|
tz := time.FixedZone("", int(tzhours*60*60+tzmins*60))
|
||||||
|
sig.When = sig.When.In(tz)
|
||||||
|
} else {
|
||||||
|
t, err := time.Parse(GitTimeLayout, string(line[dateStart:]))
|
||||||
|
if err != nil {
|
||||||
|
return Signature{}, fmt.Errorf("failed to parse git time: %w", err)
|
||||||
|
}
|
||||||
|
sig.When = t
|
||||||
|
}
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SanitizeCredentialURLs remove all credentials in URLs (starting with "scheme://")
|
||||||
|
// for the input string: "https://user:pass@domain.com" => "https://sanitized-credential@domain.com"
|
||||||
|
func SanitizeCredentialURLs(s string) string {
|
||||||
|
bs := util.StringToReadOnlyBytes(s)
|
||||||
|
schemeSepPos := bytes.Index(bs, schemeSep)
|
||||||
|
if schemeSepPos == -1 || bytes.IndexByte(bs[schemeSepPos:], '@') == -1 {
|
||||||
|
return s // fast return if there is no URL scheme or no userinfo
|
||||||
|
}
|
||||||
|
out := make([]byte, 0, len(bs)+len(userPlaceholder))
|
||||||
|
for schemeSepPos != -1 {
|
||||||
|
schemeSepPos += 3 // skip the "://"
|
||||||
|
sepAtPos := -1 // the possible '@' position: "https://foo@[^here]host"
|
||||||
|
sepEndPos := schemeSepPos // the possible end position: "The https://host[^here] in log for test"
|
||||||
|
sepLoop:
|
||||||
|
for ; sepEndPos < len(bs); sepEndPos++ {
|
||||||
|
c := bs[sepEndPos]
|
||||||
|
if ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch c {
|
||||||
|
case '@':
|
||||||
|
sepAtPos = sepEndPos
|
||||||
|
case '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '%':
|
||||||
|
continue // due to RFC 3986, userinfo can contain - . _ ~ ! $ & ' ( ) * + , ; = : and any percent-encoded chars
|
||||||
|
default:
|
||||||
|
break sepLoop // if it is an invalid char for URL (eg: space, '/', and others), stop the loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if there is '@', and the string is like "s://u@h", then hide the "u" part
|
||||||
|
if sepAtPos != -1 && (schemeSepPos >= 4 && unicode.IsLetter(rune(bs[schemeSepPos-4]))) &&
|
||||||
|
sepAtPos-schemeSepPos > 0 && sepEndPos-sepAtPos > 0 {
|
||||||
|
out = append(out, bs[:schemeSepPos]...)
|
||||||
|
out = append(out, userPlaceholder...)
|
||||||
|
out = append(out, bs[sepAtPos:sepEndPos]...)
|
||||||
|
} else {
|
||||||
|
out = append(out, bs[:sepEndPos]...)
|
||||||
|
}
|
||||||
|
bs = bs[sepEndPos:]
|
||||||
|
schemeSepPos = bytes.Index(bs, schemeSep)
|
||||||
|
}
|
||||||
|
out = append(out, bs...)
|
||||||
|
return util.BytesToReadOnlyString(out)
|
||||||
|
}
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package adapter
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -33,7 +33,7 @@ var WireSet = wire.NewSet(
|
|||||||
func ProvideLastCommitCache(
|
func ProvideLastCommitCache(
|
||||||
config types.Config,
|
config types.Config,
|
||||||
redisClient redis.UniversalClient,
|
redisClient redis.UniversalClient,
|
||||||
) (cache.Cache[CommitEntryKey, *types.Commit], error) {
|
) (cache.Cache[CommitEntryKey, *Commit], error) {
|
||||||
cacheDuration := config.LastCommitCache.Duration
|
cacheDuration := config.LastCommitCache.Duration
|
||||||
|
|
||||||
// no need to cache if it's too short
|
// no need to cache if it's too short
|
@ -88,7 +88,7 @@ func (s *Service) Blame(ctx context.Context, params *BlameParams) (<-chan *Blame
|
|||||||
|
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
|
|
||||||
reader := s.adapter.Blame(ctx,
|
reader := s.git.Blame(ctx,
|
||||||
repoPath, params.GitRef, params.Path,
|
repoPath, params.GitRef, params.Path,
|
||||||
params.LineFrom, params.LineTo)
|
params.LineFrom, params.LineTo)
|
||||||
|
|
||||||
|
@ -17,6 +17,8 @@ package git
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/git/sha"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GetBlobParams struct {
|
type GetBlobParams struct {
|
||||||
@ -26,7 +28,7 @@ type GetBlobParams struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetBlobOutput struct {
|
type GetBlobOutput struct {
|
||||||
SHA string
|
SHA sha.SHA
|
||||||
// Size is the actual size of the blob.
|
// Size is the actual size of the blob.
|
||||||
Size int64
|
Size int64
|
||||||
// ContentSize is the total number of bytes returned by the Content Reader.
|
// ContentSize is the total number of bytes returned by the Content Reader.
|
||||||
@ -43,7 +45,7 @@ func (s *Service) GetBlob(ctx context.Context, params *GetBlobParams) (*GetBlobO
|
|||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
|
|
||||||
// TODO: do we need to validate request for nil?
|
// TODO: do we need to validate request for nil?
|
||||||
reader, err := s.adapter.GetBlob(ctx, repoPath, params.SHA, params.SizeLimit)
|
reader, err := s.git.GetBlob(ctx, repoPath, sha.Must(params.SHA), params.SizeLimit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -20,9 +20,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git/adapter"
|
"github.com/harness/gitness/git/api"
|
||||||
"github.com/harness/gitness/git/check"
|
"github.com/harness/gitness/git/check"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/sha"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
@ -35,14 +35,14 @@ const (
|
|||||||
BranchSortOptionDate
|
BranchSortOptionDate
|
||||||
)
|
)
|
||||||
|
|
||||||
var listBranchesRefFields = []types.GitReferenceField{
|
var listBranchesRefFields = []api.GitReferenceField{
|
||||||
types.GitReferenceFieldRefName,
|
api.GitReferenceFieldRefName,
|
||||||
types.GitReferenceFieldObjectName,
|
api.GitReferenceFieldObjectName,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Branch struct {
|
type Branch struct {
|
||||||
Name string
|
Name string
|
||||||
SHA string
|
SHA sha.SHA
|
||||||
Commit *Commit
|
Commit *Commit
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,17 +97,17 @@ func (s *Service) CreateBranch(ctx context.Context, params *CreateBranchParams)
|
|||||||
}
|
}
|
||||||
|
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
targetCommit, err := s.adapter.GetCommit(ctx, repoPath, strings.TrimSpace(params.Target))
|
targetCommit, err := s.git.GetCommit(ctx, repoPath, strings.TrimSpace(params.Target))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get target commit: %w", err)
|
return nil, fmt.Errorf("failed to get target commit: %w", err)
|
||||||
}
|
}
|
||||||
branchRef := adapter.GetReferenceFromBranchName(params.BranchName)
|
branchRef := api.GetReferenceFromBranchName(params.BranchName)
|
||||||
err = s.adapter.UpdateRef(
|
err = s.git.UpdateRef(
|
||||||
ctx,
|
ctx,
|
||||||
params.EnvVars,
|
params.EnvVars,
|
||||||
repoPath,
|
repoPath,
|
||||||
branchRef,
|
branchRef,
|
||||||
types.NilSHA, // we want to make sure we don't overwrite any parallel create
|
sha.Nil, // we want to make sure we don't overwrite any parallel create
|
||||||
targetCommit.SHA,
|
targetCommit.SHA,
|
||||||
)
|
)
|
||||||
if errors.IsConflict(err) {
|
if errors.IsConflict(err) {
|
||||||
@ -139,7 +139,7 @@ func (s *Service) GetBranch(ctx context.Context, params *GetBranchParams) (*GetB
|
|||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
sanitizedBranchName := strings.TrimPrefix(params.BranchName, gitReferenceNamePrefixBranch)
|
sanitizedBranchName := strings.TrimPrefix(params.BranchName, gitReferenceNamePrefixBranch)
|
||||||
|
|
||||||
gitBranch, err := s.adapter.GetBranch(ctx, repoPath, sanitizedBranchName)
|
gitBranch, err := s.git.GetBranch(ctx, repoPath, sanitizedBranchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -160,17 +160,17 @@ func (s *Service) DeleteBranch(ctx context.Context, params *DeleteBranchParams)
|
|||||||
}
|
}
|
||||||
|
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
branchRef := adapter.GetReferenceFromBranchName(params.BranchName)
|
branchRef := api.GetReferenceFromBranchName(params.BranchName)
|
||||||
|
|
||||||
err := s.adapter.UpdateRef(
|
err := s.git.UpdateRef(
|
||||||
ctx,
|
ctx,
|
||||||
params.EnvVars,
|
params.EnvVars,
|
||||||
repoPath,
|
repoPath,
|
||||||
branchRef,
|
branchRef,
|
||||||
"", // delete whatever is there
|
sha.None, // delete whatever is there
|
||||||
types.NilSHA,
|
sha.Nil,
|
||||||
)
|
)
|
||||||
if types.IsNotFoundError(err) {
|
if errors.IsNotFound(err) {
|
||||||
return errors.NotFound("branch %q does not exist", params.BranchName)
|
return errors.NotFound("branch %q does not exist", params.BranchName)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -187,7 +187,7 @@ func (s *Service) ListBranches(ctx context.Context, params *ListBranchesParams)
|
|||||||
|
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
|
|
||||||
gitBranches, err := s.listBranchesLoadReferenceData(ctx, repoPath, types.BranchFilter{
|
gitBranches, err := s.listBranchesLoadReferenceData(ctx, repoPath, api.BranchFilter{
|
||||||
IncludeCommit: params.IncludeCommit,
|
IncludeCommit: params.IncludeCommit,
|
||||||
Query: params.Query,
|
Query: params.Query,
|
||||||
Sort: mapBranchesSortOption(params.Sort),
|
Sort: mapBranchesSortOption(params.Sort),
|
||||||
@ -203,17 +203,17 @@ func (s *Service) ListBranches(ctx context.Context, params *ListBranchesParams)
|
|||||||
if params.IncludeCommit {
|
if params.IncludeCommit {
|
||||||
commitSHAs := make([]string, len(gitBranches))
|
commitSHAs := make([]string, len(gitBranches))
|
||||||
for i := range gitBranches {
|
for i := range gitBranches {
|
||||||
commitSHAs[i] = gitBranches[i].SHA
|
commitSHAs[i] = gitBranches[i].SHA.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
var gitCommits []types.Commit
|
var gitCommits []*api.Commit
|
||||||
gitCommits, err = s.adapter.GetCommits(ctx, repoPath, commitSHAs)
|
gitCommits, err = s.git.GetCommits(ctx, repoPath, commitSHAs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get commit: %w", err)
|
return nil, fmt.Errorf("failed to get commit: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range gitCommits {
|
for i := range gitCommits {
|
||||||
gitBranches[i].Commit = &gitCommits[i]
|
gitBranches[i].Commit = gitCommits[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,13 +234,13 @@ func (s *Service) ListBranches(ctx context.Context, params *ListBranchesParams)
|
|||||||
func (s *Service) listBranchesLoadReferenceData(
|
func (s *Service) listBranchesLoadReferenceData(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
filter types.BranchFilter,
|
filter api.BranchFilter,
|
||||||
) ([]*types.Branch, error) {
|
) ([]*api.Branch, error) {
|
||||||
// TODO: can we be smarter with slice allocation
|
// TODO: can we be smarter with slice allocation
|
||||||
branches := make([]*types.Branch, 0, 16)
|
branches := make([]*api.Branch, 0, 16)
|
||||||
handler := listBranchesWalkReferencesHandler(&branches)
|
handler := listBranchesWalkReferencesHandler(&branches)
|
||||||
instructor, endsAfter, err := wrapInstructorWithOptionalPagination(
|
instructor, endsAfter, err := wrapInstructorWithOptionalPagination(
|
||||||
adapter.DefaultInstructor, // branches only have one target type, default instructor is enough
|
api.DefaultInstructor, // branches only have one target type, default instructor is enough
|
||||||
filter.Page,
|
filter.Page,
|
||||||
filter.PageSize,
|
filter.PageSize,
|
||||||
)
|
)
|
||||||
@ -248,7 +248,7 @@ func (s *Service) listBranchesLoadReferenceData(
|
|||||||
return nil, errors.InvalidArgument("invalid pagination details: %v", err)
|
return nil, errors.InvalidArgument("invalid pagination details: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := &types.WalkReferencesOptions{
|
opts := &api.WalkReferencesOptions{
|
||||||
Patterns: createReferenceWalkPatternsFromQuery(gitReferenceNamePrefixBranch, filter.Query),
|
Patterns: createReferenceWalkPatternsFromQuery(gitReferenceNamePrefixBranch, filter.Query),
|
||||||
Sort: filter.Sort,
|
Sort: filter.Sort,
|
||||||
Order: filter.Order,
|
Order: filter.Order,
|
||||||
@ -258,32 +258,32 @@ func (s *Service) listBranchesLoadReferenceData(
|
|||||||
MaxWalkDistance: endsAfter,
|
MaxWalkDistance: endsAfter,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.adapter.WalkReferences(ctx, repoPath, handler, opts)
|
err = s.git.WalkReferences(ctx, repoPath, handler, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to walk branch references: %w", err)
|
return nil, fmt.Errorf("failed to walk branch references: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Ctx(ctx).Trace().Msgf("git adapter returned %d branches", len(branches))
|
log.Ctx(ctx).Trace().Msgf("git api returned %d branches", len(branches))
|
||||||
|
|
||||||
return branches, nil
|
return branches, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func listBranchesWalkReferencesHandler(
|
func listBranchesWalkReferencesHandler(
|
||||||
branches *[]*types.Branch,
|
branches *[]*api.Branch,
|
||||||
) types.WalkReferencesHandler {
|
) api.WalkReferencesHandler {
|
||||||
return func(e types.WalkReferencesEntry) error {
|
return func(e api.WalkReferencesEntry) error {
|
||||||
fullRefName, ok := e[types.GitReferenceFieldRefName]
|
fullRefName, ok := e[api.GitReferenceFieldRefName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("entry missing reference name")
|
return fmt.Errorf("entry missing reference name")
|
||||||
}
|
}
|
||||||
objectSHA, ok := e[types.GitReferenceFieldObjectName]
|
objectSHA, ok := e[api.GitReferenceFieldObjectName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("entry missing object sha")
|
return fmt.Errorf("entry missing object sha")
|
||||||
}
|
}
|
||||||
|
|
||||||
branch := &types.Branch{
|
branch := &api.Branch{
|
||||||
Name: fullRefName[len(gitReferenceNamePrefixBranch):],
|
Name: fullRefName[len(gitReferenceNamePrefixBranch):],
|
||||||
SHA: objectSHA,
|
SHA: sha.Must(objectSHA),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: refactor to not use slice pointers?
|
// TODO: refactor to not use slice pointers?
|
||||||
|
@ -39,7 +39,8 @@ func (b builder) supportsEndOfOptions() bool {
|
|||||||
|
|
||||||
// descriptions is a curated list of Git command descriptions.
|
// descriptions is a curated list of Git command descriptions.
|
||||||
var descriptions = map[string]builder{
|
var descriptions = map[string]builder{
|
||||||
"am": {},
|
"am": {},
|
||||||
|
"add": {},
|
||||||
"apply": {
|
"apply": {
|
||||||
flags: NoRefUpdates,
|
flags: NoRefUpdates,
|
||||||
},
|
},
|
||||||
@ -124,6 +125,9 @@ var descriptions = map[string]builder{
|
|||||||
"log": {
|
"log": {
|
||||||
flags: NoRefUpdates,
|
flags: NoRefUpdates,
|
||||||
},
|
},
|
||||||
|
"ls-files": {
|
||||||
|
flags: NoRefUpdates,
|
||||||
|
},
|
||||||
"ls-remote": {
|
"ls-remote": {
|
||||||
flags: NoRefUpdates,
|
flags: NoRefUpdates,
|
||||||
},
|
},
|
||||||
@ -226,6 +230,9 @@ var descriptions = map[string]builder{
|
|||||||
"update-ref": {
|
"update-ref": {
|
||||||
flags: 0,
|
flags: 0,
|
||||||
},
|
},
|
||||||
|
"update-index": {
|
||||||
|
flags: NoEndOfOptions,
|
||||||
|
},
|
||||||
"upload-archive": {
|
"upload-archive": {
|
||||||
// git-upload-archive(1) has a handrolled parser which always interprets the
|
// git-upload-archive(1) has a handrolled parser which always interprets the
|
||||||
// first argument as directory, so we cannot use `--end-of-options`.
|
// first argument as directory, so we cannot use `--end-of-options`.
|
||||||
@ -240,6 +247,9 @@ var descriptions = map[string]builder{
|
|||||||
"worktree": {
|
"worktree": {
|
||||||
flags: 0,
|
flags: 0,
|
||||||
},
|
},
|
||||||
|
"write-tree": {
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// args validates the given flags and arguments and, if valid, returns the complete command line.
|
// args validates the given flags and arguments and, if valid, returns the complete command line.
|
||||||
@ -248,7 +258,7 @@ func (b builder) args(flags []string, args []string, postSepArgs []string) ([]st
|
|||||||
|
|
||||||
cmdArgs = append(cmdArgs, flags...)
|
cmdArgs = append(cmdArgs, flags...)
|
||||||
|
|
||||||
if b.supportsEndOfOptions() {
|
if b.supportsEndOfOptions() && len(flags) > 0 {
|
||||||
cmdArgs = append(cmdArgs, "--end-of-options")
|
cmdArgs = append(cmdArgs, "--end-of-options")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +77,33 @@ func New(name string, options ...CmdOptionFunc) *Command {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone clones the command object.
|
||||||
|
func (c *Command) Clone() *Command {
|
||||||
|
flags := make([]string, len(c.Flags))
|
||||||
|
copy(flags, c.Flags)
|
||||||
|
|
||||||
|
args := make([]string, len(c.Args))
|
||||||
|
copy(args, c.Args)
|
||||||
|
|
||||||
|
postSepArgs := make([]string, len(c.PostSepArgs))
|
||||||
|
copy(postSepArgs, c.Flags)
|
||||||
|
|
||||||
|
envs := make(Envs, len(c.Envs))
|
||||||
|
for key, val := range c.Envs {
|
||||||
|
envs[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Command{
|
||||||
|
Name: c.Name,
|
||||||
|
Action: c.Action,
|
||||||
|
Flags: flags,
|
||||||
|
Args: args,
|
||||||
|
PostSepArgs: postSepArgs,
|
||||||
|
Envs: envs,
|
||||||
|
configEnvCounter: c.configEnvCounter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add appends given options to the command.
|
// Add appends given options to the command.
|
||||||
func (c *Command) Add(options ...CmdOptionFunc) *Command {
|
func (c *Command) Add(options ...CmdOptionFunc) *Command {
|
||||||
for _, opt := range options {
|
for _, opt := range options {
|
||||||
@ -109,6 +136,7 @@ func (c *Command) Run(ctx context.Context, opts ...RunOptionFunc) (err error) {
|
|||||||
if len(c.Envs) > 0 {
|
if len(c.Envs) > 0 {
|
||||||
cmd.Env = c.Envs.Args()
|
cmd.Env = c.Envs.Args()
|
||||||
}
|
}
|
||||||
|
cmd.Env = append(cmd.Env, options.Envs...)
|
||||||
cmd.Dir = options.Dir
|
cmd.Dir = options.Dir
|
||||||
cmd.Stdin = options.Stdin
|
cmd.Stdin = options.Stdin
|
||||||
cmd.Stdout = options.Stdout
|
cmd.Stdout = options.Stdout
|
||||||
|
@ -28,6 +28,9 @@ const (
|
|||||||
GitTracePerformance = "GIT_TRACE_PERFORMANCE"
|
GitTracePerformance = "GIT_TRACE_PERFORMANCE"
|
||||||
GitTraceSetup = "GIT_TRACE_SETUP"
|
GitTraceSetup = "GIT_TRACE_SETUP"
|
||||||
GitExecPath = "GIT_EXEC_PATH" // tells Git where to find its binaries.
|
GitExecPath = "GIT_EXEC_PATH" // tells Git where to find its binaries.
|
||||||
|
|
||||||
|
GitObjectDir = "GIT_OBJECT_DIRECTORY"
|
||||||
|
GitAlternateObjectDirs = "GIT_ALTERNATE_OBJECT_DIRECTORIES"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Envs custom key value store for environment variables.
|
// Envs custom key value store for environment variables.
|
||||||
|
@ -48,6 +48,10 @@ func (e *Error) ExitCode() int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Error) IsExitCode(code int) bool {
|
||||||
|
return e.ExitCode() == code
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
func (e *Error) Error() string {
|
||||||
if len(e.StdErr) != 0 {
|
if len(e.StdErr) != 0 {
|
||||||
return fmt.Sprintf("%s: %s", e.Err.Error(), e.StdErr)
|
return fmt.Sprintf("%s: %s", e.Err.Error(), e.StdErr)
|
||||||
|
@ -17,6 +17,7 @@ package command
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -105,6 +106,15 @@ func WithConfig(key, value string) CmdOptionFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithAlternateObjectDirs function sets alternates directories for object access.
|
||||||
|
func WithAlternateObjectDirs(dirs ...string) CmdOptionFunc {
|
||||||
|
return func(c *Command) {
|
||||||
|
if len(dirs) > 0 {
|
||||||
|
c.Envs[GitAlternateObjectDirs] = strings.Join(dirs, ":")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RunOption contains option for running a command.
|
// RunOption contains option for running a command.
|
||||||
type RunOption struct {
|
type RunOption struct {
|
||||||
// Dir is location of repo.
|
// Dir is location of repo.
|
||||||
@ -115,6 +125,9 @@ type RunOption struct {
|
|||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
// Stderr is the error output from the command.
|
// Stderr is the error output from the command.
|
||||||
Stderr io.Writer
|
Stderr io.Writer
|
||||||
|
// Envs is environments slice containing (final) immutable
|
||||||
|
// environment pair "ENV=value"
|
||||||
|
Envs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunOptionFunc func(option *RunOption)
|
type RunOptionFunc func(option *RunOption)
|
||||||
@ -147,3 +160,11 @@ func WithStderr(stderr io.Writer) RunOptionFunc {
|
|||||||
option.Stderr = stderr
|
option.Stderr = stderr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithEnvs sets immutable values as slice, it is always added
|
||||||
|
// et the end of env slice.
|
||||||
|
func WithEnvs(envs ...string) RunOptionFunc {
|
||||||
|
return func(option *RunOption) {
|
||||||
|
option.Envs = append(option.Envs, envs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,19 +20,19 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
|
"github.com/harness/gitness/git/api"
|
||||||
"github.com/harness/gitness/git/enum"
|
"github.com/harness/gitness/git/enum"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/sha"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GetCommitParams struct {
|
type GetCommitParams struct {
|
||||||
ReadParams
|
ReadParams
|
||||||
// SHA is the git commit sha
|
Revision string
|
||||||
SHA string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Commit struct {
|
type Commit struct {
|
||||||
SHA string `json:"sha"`
|
SHA sha.SHA `json:"sha"`
|
||||||
ParentSHAs []string `json:"parent_shas,omitempty"`
|
ParentSHAs []sha.SHA `json:"parent_shas,omitempty"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
Author Signature `json:"author"`
|
Author Signature `json:"author"`
|
||||||
@ -70,11 +70,8 @@ func (s *Service) GetCommit(ctx context.Context, params *GetCommitParams) (*GetC
|
|||||||
if params == nil {
|
if params == nil {
|
||||||
return nil, ErrNoParamsProvided
|
return nil, ErrNoParamsProvided
|
||||||
}
|
}
|
||||||
if !isValidGitSHA(params.SHA) {
|
|
||||||
return nil, errors.InvalidArgument("the provided commit sha '%s' is of invalid format.", params.SHA)
|
|
||||||
}
|
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
result, err := s.adapter.GetCommit(ctx, repoPath, params.SHA)
|
result, err := s.git.GetCommit(ctx, repoPath, params.Revision)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -116,8 +113,8 @@ type ListCommitsParams struct {
|
|||||||
type RenameDetails struct {
|
type RenameDetails struct {
|
||||||
OldPath string
|
OldPath string
|
||||||
NewPath string
|
NewPath string
|
||||||
CommitShaBefore string
|
CommitShaBefore sha.SHA
|
||||||
CommitShaAfter string
|
CommitShaAfter sha.SHA
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListCommitsOutput struct {
|
type ListCommitsOutput struct {
|
||||||
@ -141,14 +138,14 @@ func (s *Service) ListCommits(ctx context.Context, params *ListCommitsParams) (*
|
|||||||
|
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
|
|
||||||
gitCommits, renameDetails, err := s.adapter.ListCommits(
|
gitCommits, renameDetails, err := s.git.ListCommits(
|
||||||
ctx,
|
ctx,
|
||||||
repoPath,
|
repoPath,
|
||||||
params.GitREF,
|
params.GitREF,
|
||||||
int(params.Page),
|
int(params.Page),
|
||||||
int(params.Limit),
|
int(params.Limit),
|
||||||
params.IncludeStats,
|
params.IncludeStats,
|
||||||
types.CommitFilter{
|
api.CommitFilter{
|
||||||
AfterRef: params.After,
|
AfterRef: params.After,
|
||||||
Path: params.Path,
|
Path: params.Path,
|
||||||
Since: params.Since,
|
Since: params.Since,
|
||||||
@ -165,7 +162,7 @@ func (s *Service) ListCommits(ctx context.Context, params *ListCommitsParams) (*
|
|||||||
if params.Page == 1 && len(gitCommits) < int(params.Limit) {
|
if params.Page == 1 && len(gitCommits) < int(params.Limit) {
|
||||||
totalCommits = len(gitCommits)
|
totalCommits = len(gitCommits)
|
||||||
} else if params.After != "" && params.GitREF != params.After {
|
} else if params.After != "" && params.GitREF != params.After {
|
||||||
div, err := s.adapter.GetCommitDivergences(ctx, repoPath, []types.CommitDivergenceRequest{
|
div, err := s.git.GetCommitDivergences(ctx, repoPath, []api.CommitDivergenceRequest{
|
||||||
{From: params.GitREF, To: params.After},
|
{From: params.GitREF, To: params.After},
|
||||||
}, 0)
|
}, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -178,7 +175,7 @@ func (s *Service) ListCommits(ctx context.Context, params *ListCommitsParams) (*
|
|||||||
|
|
||||||
commits := make([]Commit, len(gitCommits))
|
commits := make([]Commit, len(gitCommits))
|
||||||
for i := range gitCommits {
|
for i := range gitCommits {
|
||||||
commit, err := mapCommit(&gitCommits[i])
|
commit, err := mapCommit(gitCommits[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to map rpc commit: %w", err)
|
return nil, fmt.Errorf("failed to map rpc commit: %w", err)
|
||||||
}
|
}
|
||||||
@ -200,7 +197,7 @@ type GetCommitDivergencesParams struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetCommitDivergencesOutput struct {
|
type GetCommitDivergencesOutput struct {
|
||||||
Divergences []types.CommitDivergence
|
Divergences []api.CommitDivergence
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitDivergenceRequest contains the refs for which the converging commits should be counted.
|
// CommitDivergenceRequest contains the refs for which the converging commits should be counted.
|
||||||
@ -229,16 +226,15 @@ func (s *Service) GetCommitDivergences(
|
|||||||
|
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
|
|
||||||
requests := make([]types.CommitDivergenceRequest, len(params.Requests))
|
requests := make([]api.CommitDivergenceRequest, len(params.Requests))
|
||||||
for i, req := range params.Requests {
|
for i, req := range params.Requests {
|
||||||
requests[i] = types.CommitDivergenceRequest{
|
requests[i] = api.CommitDivergenceRequest{
|
||||||
From: req.From,
|
From: req.From,
|
||||||
To: req.To,
|
To: req.To,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// call gitea
|
divergences, err := s.git.GetCommitDivergences(
|
||||||
divergences, err := s.adapter.GetCommitDivergences(
|
|
||||||
ctx,
|
ctx,
|
||||||
repoPath,
|
repoPath,
|
||||||
requests,
|
requests,
|
||||||
|
@ -20,7 +20,8 @@ import (
|
|||||||
|
|
||||||
// ReadParams contains the base parameters for read operations.
|
// ReadParams contains the base parameters for read operations.
|
||||||
type ReadParams struct {
|
type ReadParams struct {
|
||||||
RepoUID string
|
RepoUID string
|
||||||
|
AlternateObjectDirs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ReadParams) Validate() error {
|
func (p ReadParams) Validate() error {
|
||||||
|
39
git/diff.go
39
git/diff.go
@ -22,9 +22,11 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
|
"github.com/harness/gitness/git/api"
|
||||||
"github.com/harness/gitness/git/diff"
|
"github.com/harness/gitness/git/diff"
|
||||||
"github.com/harness/gitness/git/enum"
|
"github.com/harness/gitness/git/enum"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/parser"
|
||||||
|
"github.com/harness/gitness/git/sha"
|
||||||
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
@ -52,19 +54,27 @@ func (s *Service) RawDiff(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
out io.Writer,
|
out io.Writer,
|
||||||
params *DiffParams,
|
params *DiffParams,
|
||||||
files ...types.FileDiffRequest,
|
files ...api.FileDiffRequest,
|
||||||
) error {
|
) error {
|
||||||
return s.rawDiff(ctx, out, params, files...)
|
return s.rawDiff(ctx, out, params, files...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) rawDiff(ctx context.Context, w io.Writer, params *DiffParams, files ...types.FileDiffRequest) error {
|
func (s *Service) rawDiff(ctx context.Context, w io.Writer, params *DiffParams, files ...api.FileDiffRequest) error {
|
||||||
if err := params.Validate(); err != nil {
|
if err := params.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
|
|
||||||
err := s.adapter.RawDiff(ctx, w, repoPath, params.BaseRef, params.HeadRef, params.MergeBase, files...)
|
err := s.git.RawDiff(ctx,
|
||||||
|
w,
|
||||||
|
repoPath,
|
||||||
|
params.BaseRef,
|
||||||
|
params.HeadRef,
|
||||||
|
params.MergeBase,
|
||||||
|
params.AlternateObjectDirs,
|
||||||
|
files...,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -72,11 +82,8 @@ func (s *Service) rawDiff(ctx context.Context, w io.Writer, params *DiffParams,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) CommitDiff(ctx context.Context, params *GetCommitParams, out io.Writer) error {
|
func (s *Service) CommitDiff(ctx context.Context, params *GetCommitParams, out io.Writer) error {
|
||||||
if !isValidGitSHA(params.SHA) {
|
|
||||||
return errors.InvalidArgument("the provided commit sha '%s' is of invalid format.", params.SHA)
|
|
||||||
}
|
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
err := s.adapter.CommitDiff(ctx, repoPath, params.SHA, out)
|
err := s.git.CommitDiff(ctx, repoPath, params.Revision, out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -95,7 +102,7 @@ func (s *Service) DiffShortStat(ctx context.Context, params *DiffParams) (DiffSh
|
|||||||
return DiffShortStatOutput{}, err
|
return DiffShortStatOutput{}, err
|
||||||
}
|
}
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
stat, err := s.adapter.DiffShortStat(ctx,
|
stat, err := s.git.DiffShortStat(ctx,
|
||||||
repoPath,
|
repoPath,
|
||||||
params.BaseRef,
|
params.BaseRef,
|
||||||
params.HeadRef,
|
params.HeadRef,
|
||||||
@ -207,7 +214,7 @@ func (s *Service) GetDiffHunkHeaders(
|
|||||||
|
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
|
|
||||||
hunkHeaders, err := s.adapter.GetDiffHunkHeaders(ctx, repoPath, params.TargetCommitSHA, params.SourceCommitSHA)
|
hunkHeaders, err := s.git.GetDiffHunkHeaders(ctx, repoPath, params.TargetCommitSHA, params.SourceCommitSHA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GetDiffHunkHeadersOutput{}, err
|
return GetDiffHunkHeadersOutput{}, err
|
||||||
}
|
}
|
||||||
@ -233,7 +240,7 @@ type DiffCutOutput struct {
|
|||||||
Header HunkHeader
|
Header HunkHeader
|
||||||
LinesHeader string
|
LinesHeader string
|
||||||
Lines []string
|
Lines []string
|
||||||
MergeBaseSHA string
|
MergeBaseSHA sha.SHA
|
||||||
}
|
}
|
||||||
|
|
||||||
type DiffCutParams struct {
|
type DiffCutParams struct {
|
||||||
@ -256,17 +263,17 @@ func (s *Service) DiffCut(ctx context.Context, params *DiffCutParams) (DiffCutOu
|
|||||||
|
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
|
|
||||||
mergeBaseSHA, _, err := s.adapter.GetMergeBase(ctx, repoPath, "", params.TargetCommitSHA, params.SourceCommitSHA)
|
mergeBaseSHA, _, err := s.git.GetMergeBase(ctx, repoPath, "", params.TargetCommitSHA, params.SourceCommitSHA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DiffCutOutput{}, fmt.Errorf("DiffCut: failed to find merge base: %w", err)
|
return DiffCutOutput{}, fmt.Errorf("DiffCut: failed to find merge base: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
header, linesHunk, err := s.adapter.DiffCut(ctx,
|
header, linesHunk, err := s.git.DiffCut(ctx,
|
||||||
repoPath,
|
repoPath,
|
||||||
params.TargetCommitSHA,
|
params.TargetCommitSHA,
|
||||||
params.SourceCommitSHA,
|
params.SourceCommitSHA,
|
||||||
params.Path,
|
params.Path,
|
||||||
types.DiffCutParams{
|
parser.DiffCutParams{
|
||||||
LineStart: params.LineStart,
|
LineStart: params.LineStart,
|
||||||
LineStartNew: params.LineStartNew,
|
LineStartNew: params.LineStartNew,
|
||||||
LineEnd: params.LineEnd,
|
LineEnd: params.LineEnd,
|
||||||
@ -328,7 +335,7 @@ func parseFileDiffStatus(ftype diff.FileType) enum.FileDiffStatus {
|
|||||||
func (s *Service) Diff(
|
func (s *Service) Diff(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
params *DiffParams,
|
params *DiffParams,
|
||||||
files ...types.FileDiffRequest,
|
files ...api.FileDiffRequest,
|
||||||
) (<-chan *FileDiff, <-chan error) {
|
) (<-chan *FileDiff, <-chan error) {
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
ch := make(chan *FileDiff)
|
ch := make(chan *FileDiff)
|
||||||
@ -403,7 +410,7 @@ func (s *Service) DiffFileNames(ctx context.Context, params *DiffParams) (DiffFi
|
|||||||
return DiffFileNamesOutput{}, err
|
return DiffFileNamesOutput{}, err
|
||||||
}
|
}
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
fileNames, err := s.adapter.DiffFileName(
|
fileNames, err := s.git.DiffFileName(
|
||||||
ctx,
|
ctx,
|
||||||
repoPath,
|
repoPath,
|
||||||
params.BaseRef,
|
params.BaseRef,
|
||||||
|
@ -23,6 +23,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/git/sha"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CLICore implements the core of a githook cli. It uses the client and execution timeout
|
// CLICore implements the core of a githook cli. It uses the client and execution timeout
|
||||||
@ -61,8 +63,8 @@ func (c *CLICore) Update(ctx context.Context, ref string, oldSHA string, newSHA
|
|||||||
in := UpdateInput{
|
in := UpdateInput{
|
||||||
RefUpdate: ReferenceUpdate{
|
RefUpdate: ReferenceUpdate{
|
||||||
Ref: ref,
|
Ref: ref,
|
||||||
Old: oldSHA,
|
Old: sha.Must(oldSHA),
|
||||||
New: newSHA,
|
New: sha.Must(newSHA),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,8 +141,8 @@ func getUpdatedReferencesFromStdIn() ([]ReferenceUpdate, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updatedRefs = append(updatedRefs, ReferenceUpdate{
|
updatedRefs = append(updatedRefs, ReferenceUpdate{
|
||||||
Old: splitGitHookData[0],
|
Old: sha.Must(splitGitHookData[0]),
|
||||||
New: splitGitHookData[1],
|
New: sha.Must(splitGitHookData[1]),
|
||||||
Ref: splitGitHookData[2],
|
Ref: splitGitHookData[2],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
package hook
|
package hook
|
||||||
|
|
||||||
|
import "github.com/harness/gitness/git/sha"
|
||||||
|
|
||||||
// Output represents the output of server hook api calls.
|
// Output represents the output of server hook api calls.
|
||||||
type Output struct {
|
type Output struct {
|
||||||
// Messages contains standard user facing messages.
|
// Messages contains standard user facing messages.
|
||||||
@ -28,9 +30,9 @@ type ReferenceUpdate struct {
|
|||||||
// Ref is the full name of the reference that got updated.
|
// Ref is the full name of the reference that got updated.
|
||||||
Ref string `json:"ref"`
|
Ref string `json:"ref"`
|
||||||
// Old is the old commmit hash (before the update).
|
// Old is the old commmit hash (before the update).
|
||||||
Old string `json:"old"`
|
Old sha.SHA `json:"old"`
|
||||||
// New is the new commit hash (after the update).
|
// New is the new commit hash (after the update).
|
||||||
New string `json:"new"`
|
New sha.SHA `json:"new"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostReceiveInput represents the input of the post-receive git hook.
|
// PostReceiveInput represents the input of the post-receive git hook.
|
||||||
|
@ -43,7 +43,7 @@ func (s *Service) GetInfoRefs(ctx context.Context, w io.Writer, params *InfoRefs
|
|||||||
}
|
}
|
||||||
|
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
err := s.adapter.InfoRefs(ctx, repoPath, params.Service, w, environ...)
|
err := s.git.InfoRefs(ctx, repoPath, params.Service, w, environ...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to fetch info references: %w", err)
|
return fmt.Errorf("failed to fetch info references: %w", err)
|
||||||
}
|
}
|
||||||
@ -96,7 +96,7 @@ func (s *Service) ServicePack(ctx context.Context, w io.Writer, params *ServiceP
|
|||||||
env = append(env, "GIT_PROTOCOL="+params.GitProtocol)
|
env = append(env, "GIT_PROTOCOL="+params.GitProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.adapter.ServicePack(ctx, repoPath, params.Service, params.Data, w, env...)
|
err := s.git.ServicePack(ctx, repoPath, params.Service, params.Data, w, env...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute git %s: %w", params.Service, err)
|
return fmt.Errorf("failed to execute git %s: %w", params.Service, err)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Interface interface {
|
type Interface interface {
|
||||||
@ -68,8 +68,8 @@ type Interface interface {
|
|||||||
/*
|
/*
|
||||||
* Diff services
|
* Diff services
|
||||||
*/
|
*/
|
||||||
RawDiff(ctx context.Context, w io.Writer, in *DiffParams, files ...types.FileDiffRequest) error
|
RawDiff(ctx context.Context, w io.Writer, in *DiffParams, files ...api.FileDiffRequest) error
|
||||||
Diff(ctx context.Context, in *DiffParams, files ...types.FileDiffRequest) (<-chan *FileDiff, <-chan error)
|
Diff(ctx context.Context, in *DiffParams, files ...api.FileDiffRequest) (<-chan *FileDiff, <-chan error)
|
||||||
DiffFileNames(ctx context.Context, in *DiffParams) (DiffFileNamesOutput, error)
|
DiffFileNames(ctx context.Context, in *DiffParams) (DiffFileNamesOutput, error)
|
||||||
CommitDiff(ctx context.Context, params *GetCommitParams, w io.Writer) error
|
CommitDiff(ctx context.Context, params *GetCommitParams, w io.Writer) error
|
||||||
DiffShortStat(ctx context.Context, params *DiffParams) (DiffShortStatOutput, error)
|
DiffShortStat(ctx context.Context, params *DiffParams) (DiffShortStatOutput, error)
|
||||||
|
@ -17,10 +17,11 @@ package git
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/api"
|
||||||
|
"github.com/harness/gitness/git/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mapBranch(b *types.Branch) (*Branch, error) {
|
func mapBranch(b *api.Branch) (*Branch, error) {
|
||||||
if b == nil {
|
if b == nil {
|
||||||
return nil, fmt.Errorf("rpc branch is nil")
|
return nil, fmt.Errorf("rpc branch is nil")
|
||||||
}
|
}
|
||||||
@ -41,7 +42,7 @@ func mapBranch(b *types.Branch) (*Branch, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapCommit(c *types.Commit) (*Commit, error) {
|
func mapCommit(c *api.Commit) (*Commit, error) {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return nil, fmt.Errorf("rpc commit is nil")
|
return nil, fmt.Errorf("rpc commit is nil")
|
||||||
}
|
}
|
||||||
@ -67,12 +68,12 @@ func mapCommit(c *types.Commit) (*Commit, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapFileStats(typeStats []types.CommitFileStats) []CommitFileStats {
|
func mapFileStats(typeStats []api.CommitFileStats) []CommitFileStats {
|
||||||
var stats = make([]CommitFileStats, len(typeStats))
|
var stats = make([]CommitFileStats, len(typeStats))
|
||||||
|
|
||||||
for i, tStat := range typeStats {
|
for i, tStat := range typeStats {
|
||||||
stats[i] = CommitFileStats{
|
stats[i] = CommitFileStats{
|
||||||
Status: tStat.Status,
|
Status: tStat.ChangeType,
|
||||||
Path: tStat.Path,
|
Path: tStat.Path,
|
||||||
OldPath: tStat.OldPath,
|
OldPath: tStat.OldPath,
|
||||||
Insertions: tStat.Insertions,
|
Insertions: tStat.Insertions,
|
||||||
@ -83,7 +84,7 @@ func mapFileStats(typeStats []types.CommitFileStats) []CommitFileStats {
|
|||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapSignature(s *types.Signature) (*Signature, error) {
|
func mapSignature(s *api.Signature) (*Signature, error) {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil, fmt.Errorf("rpc signature is nil")
|
return nil, fmt.Errorf("rpc signature is nil")
|
||||||
}
|
}
|
||||||
@ -99,7 +100,7 @@ func mapSignature(s *types.Signature) (*Signature, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapIdentity(id *types.Identity) (Identity, error) {
|
func mapIdentity(id *api.Identity) (Identity, error) {
|
||||||
if id == nil {
|
if id == nil {
|
||||||
return Identity{}, fmt.Errorf("rpc identity is nil")
|
return Identity{}, fmt.Errorf("rpc identity is nil")
|
||||||
}
|
}
|
||||||
@ -110,12 +111,12 @@ func mapIdentity(id *types.Identity) (Identity, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapBranchesSortOption(o BranchSortOption) types.GitReferenceField {
|
func mapBranchesSortOption(o BranchSortOption) api.GitReferenceField {
|
||||||
switch o {
|
switch o {
|
||||||
case BranchSortOptionName:
|
case BranchSortOptionName:
|
||||||
return types.GitReferenceFieldObjectName
|
return api.GitReferenceFieldObjectName
|
||||||
case BranchSortOptionDate:
|
case BranchSortOptionDate:
|
||||||
return types.GitReferenceFieldCreatorDate
|
return api.GitReferenceFieldCreatorDate
|
||||||
case BranchSortOptionDefault:
|
case BranchSortOptionDefault:
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
@ -124,7 +125,7 @@ func mapBranchesSortOption(o BranchSortOption) types.GitReferenceField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapAnnotatedTag(tag *types.Tag) *CommitTag {
|
func mapAnnotatedTag(tag *api.Tag) *CommitTag {
|
||||||
tagger, _ := mapSignature(&tag.Tagger)
|
tagger, _ := mapSignature(&tag.Tagger)
|
||||||
return &CommitTag{
|
return &CommitTag{
|
||||||
Name: tag.Name,
|
Name: tag.Name,
|
||||||
@ -137,21 +138,21 @@ func mapAnnotatedTag(tag *types.Tag) *CommitTag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapListCommitTagsSortOption(s TagSortOption) types.GitReferenceField {
|
func mapListCommitTagsSortOption(s TagSortOption) api.GitReferenceField {
|
||||||
switch s {
|
switch s {
|
||||||
case TagSortOptionDate:
|
case TagSortOptionDate:
|
||||||
return types.GitReferenceFieldCreatorDate
|
return api.GitReferenceFieldCreatorDate
|
||||||
case TagSortOptionName:
|
case TagSortOptionName:
|
||||||
return types.GitReferenceFieldRefName
|
return api.GitReferenceFieldRefName
|
||||||
case TagSortOptionDefault:
|
case TagSortOptionDefault:
|
||||||
return types.GitReferenceFieldRefName
|
return api.GitReferenceFieldRefName
|
||||||
default:
|
default:
|
||||||
// no need to error out - just use default for sorting
|
// no need to error out - just use default for sorting
|
||||||
return types.GitReferenceFieldRefName
|
return api.GitReferenceFieldRefName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapTreeNode(n *types.TreeNode) (TreeNode, error) {
|
func mapTreeNode(n *api.TreeNode) (TreeNode, error) {
|
||||||
if n == nil {
|
if n == nil {
|
||||||
return TreeNode{}, fmt.Errorf("rpc tree node is nil")
|
return TreeNode{}, fmt.Errorf("rpc tree node is nil")
|
||||||
}
|
}
|
||||||
@ -169,43 +170,43 @@ func mapTreeNode(n *types.TreeNode) (TreeNode, error) {
|
|||||||
return TreeNode{
|
return TreeNode{
|
||||||
Type: nodeType,
|
Type: nodeType,
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
SHA: n.Sha,
|
SHA: n.SHA.String(),
|
||||||
Name: n.Name,
|
Name: n.Name,
|
||||||
Path: n.Path,
|
Path: n.Path,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapTreeNodeType(t types.TreeNodeType) (TreeNodeType, error) {
|
func mapTreeNodeType(t api.TreeNodeType) (TreeNodeType, error) {
|
||||||
switch t {
|
switch t {
|
||||||
case types.TreeNodeTypeBlob:
|
case api.TreeNodeTypeBlob:
|
||||||
return TreeNodeTypeBlob, nil
|
return TreeNodeTypeBlob, nil
|
||||||
case types.TreeNodeTypeCommit:
|
case api.TreeNodeTypeCommit:
|
||||||
return TreeNodeTypeCommit, nil
|
return TreeNodeTypeCommit, nil
|
||||||
case types.TreeNodeTypeTree:
|
case api.TreeNodeTypeTree:
|
||||||
return TreeNodeTypeTree, nil
|
return TreeNodeTypeTree, nil
|
||||||
default:
|
default:
|
||||||
return TreeNodeTypeBlob, fmt.Errorf("unknown rpc tree node type: %d", t)
|
return TreeNodeTypeBlob, fmt.Errorf("unknown rpc tree node type: %d", t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapTreeNodeMode(m types.TreeNodeMode) (TreeNodeMode, error) {
|
func mapTreeNodeMode(m api.TreeNodeMode) (TreeNodeMode, error) {
|
||||||
switch m {
|
switch m {
|
||||||
case types.TreeNodeModeFile:
|
case api.TreeNodeModeFile:
|
||||||
return TreeNodeModeFile, nil
|
return TreeNodeModeFile, nil
|
||||||
case types.TreeNodeModeExec:
|
case api.TreeNodeModeExec:
|
||||||
return TreeNodeModeExec, nil
|
return TreeNodeModeExec, nil
|
||||||
case types.TreeNodeModeSymlink:
|
case api.TreeNodeModeSymlink:
|
||||||
return TreeNodeModeSymlink, nil
|
return TreeNodeModeSymlink, nil
|
||||||
case types.TreeNodeModeCommit:
|
case api.TreeNodeModeCommit:
|
||||||
return TreeNodeModeCommit, nil
|
return TreeNodeModeCommit, nil
|
||||||
case types.TreeNodeModeTree:
|
case api.TreeNodeModeTree:
|
||||||
return TreeNodeModeTree, nil
|
return TreeNodeModeTree, nil
|
||||||
default:
|
default:
|
||||||
return TreeNodeModeFile, fmt.Errorf("unknown rpc tree node mode: %d", m)
|
return TreeNodeModeFile, fmt.Errorf("unknown rpc tree node mode: %d", m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapRenameDetails(c []types.PathRenameDetails) []*RenameDetails {
|
func mapRenameDetails(c []api.PathRenameDetails) []*RenameDetails {
|
||||||
renameDetailsList := make([]*RenameDetails, len(c))
|
renameDetailsList := make([]*RenameDetails, len(c))
|
||||||
for i, detail := range c {
|
for i, detail := range c {
|
||||||
renameDetailsList[i] = &RenameDetails{
|
renameDetailsList[i] = &RenameDetails{
|
||||||
@ -218,21 +219,21 @@ func mapRenameDetails(c []types.PathRenameDetails) []*RenameDetails {
|
|||||||
return renameDetailsList
|
return renameDetailsList
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapToSortOrder(o SortOrder) types.SortOrder {
|
func mapToSortOrder(o SortOrder) api.SortOrder {
|
||||||
switch o {
|
switch o {
|
||||||
case SortOrderAsc:
|
case SortOrderAsc:
|
||||||
return types.SortOrderAsc
|
return api.SortOrderAsc
|
||||||
case SortOrderDesc:
|
case SortOrderDesc:
|
||||||
return types.SortOrderDesc
|
return api.SortOrderDesc
|
||||||
case SortOrderDefault:
|
case SortOrderDefault:
|
||||||
return types.SortOrderDefault
|
return api.SortOrderDefault
|
||||||
default:
|
default:
|
||||||
// no need to error out - just use default for sorting
|
// no need to error out - just use default for sorting
|
||||||
return types.SortOrderDefault
|
return api.SortOrderDefault
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapHunkHeader(h *types.HunkHeader) HunkHeader {
|
func mapHunkHeader(h *parser.HunkHeader) HunkHeader {
|
||||||
return HunkHeader{
|
return HunkHeader{
|
||||||
OldLine: h.OldLine,
|
OldLine: h.OldLine,
|
||||||
OldSpan: h.OldSpan,
|
OldSpan: h.OldSpan,
|
||||||
@ -242,7 +243,7 @@ func mapHunkHeader(h *types.HunkHeader) HunkHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapDiffFileHeader(h *types.DiffFileHeader) DiffFileHeader {
|
func mapDiffFileHeader(h *parser.DiffFileHeader) DiffFileHeader {
|
||||||
return DiffFileHeader{
|
return DiffFileHeader{
|
||||||
OldName: h.OldFileName,
|
OldName: h.OldFileName,
|
||||||
NewName: h.NewFileName,
|
NewName: h.NewFileName,
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MatchFilesParams struct {
|
type MatchFilesParams struct {
|
||||||
@ -30,7 +30,7 @@ type MatchFilesParams struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MatchFilesOutput struct {
|
type MatchFilesOutput struct {
|
||||||
Files []types.FileContent
|
Files []api.FileContent
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) MatchFiles(ctx context.Context,
|
func (s *Service) MatchFiles(ctx context.Context,
|
||||||
@ -38,7 +38,7 @@ func (s *Service) MatchFiles(ctx context.Context,
|
|||||||
) (*MatchFilesOutput, error) {
|
) (*MatchFilesOutput, error) {
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
|
|
||||||
matchedFiles, err := s.adapter.MatchFiles(ctx, repoPath,
|
matchedFiles, err := s.git.MatchFiles(ctx, repoPath,
|
||||||
params.Ref, params.DirPath, params.Pattern, params.MaxSize)
|
params.Ref, params.DirPath, params.Pattern, params.MaxSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("MatchFiles: failed to open repo: %w", err)
|
return nil, fmt.Errorf("MatchFiles: failed to open repo: %w", err)
|
||||||
|
60
git/merge.go
60
git/merge.go
@ -21,9 +21,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
|
"github.com/harness/gitness/git/api"
|
||||||
"github.com/harness/gitness/git/enum"
|
"github.com/harness/gitness/git/enum"
|
||||||
"github.com/harness/gitness/git/merge"
|
"github.com/harness/gitness/git/merge"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/sha"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
@ -57,7 +58,7 @@ type MergeParams struct {
|
|||||||
|
|
||||||
// HeadExpectedSHA is commit sha on the head branch, if HeadExpectedSHA is older
|
// HeadExpectedSHA is commit sha on the head branch, if HeadExpectedSHA is older
|
||||||
// than the HeadBranch latest sha then merge will fail.
|
// than the HeadBranch latest sha then merge will fail.
|
||||||
HeadExpectedSHA string
|
HeadExpectedSHA sha.SHA
|
||||||
|
|
||||||
Force bool
|
Force bool
|
||||||
DeleteHeadBranch bool
|
DeleteHeadBranch bool
|
||||||
@ -88,13 +89,13 @@ func (p *MergeParams) Validate() error {
|
|||||||
// base, head and commit sha.
|
// base, head and commit sha.
|
||||||
type MergeOutput struct {
|
type MergeOutput struct {
|
||||||
// BaseSHA is the sha of the latest commit on the base branch that was used for merging.
|
// BaseSHA is the sha of the latest commit on the base branch that was used for merging.
|
||||||
BaseSHA string
|
BaseSHA sha.SHA
|
||||||
// HeadSHA is the sha of the latest commit on the head branch that was used for merging.
|
// HeadSHA is the sha of the latest commit on the head branch that was used for merging.
|
||||||
HeadSHA string
|
HeadSHA sha.SHA
|
||||||
// MergeBaseSHA is the sha of the merge base of the HeadSHA and BaseSHA
|
// MergeBaseSHA is the sha of the merge base of the HeadSHA and BaseSHA
|
||||||
MergeBaseSHA string
|
MergeBaseSHA sha.SHA
|
||||||
// MergeSHA is the sha of the commit after merging HeadSHA with BaseSHA.
|
// MergeSHA is the sha of the commit after merging HeadSHA with BaseSHA.
|
||||||
MergeSHA string
|
MergeSHA sha.SHA
|
||||||
|
|
||||||
CommitCount int
|
CommitCount int
|
||||||
ChangedFileCount int
|
ChangedFileCount int
|
||||||
@ -148,7 +149,7 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
|
|||||||
// set up the target reference
|
// set up the target reference
|
||||||
|
|
||||||
var refPath string
|
var refPath string
|
||||||
var refOldValue string
|
var refOldValue sha.SHA
|
||||||
|
|
||||||
if params.RefType != enum.RefTypeUndefined {
|
if params.RefType != enum.RefTypeUndefined {
|
||||||
refPath, err = GetRefPath(params.RefName, params.RefType)
|
refPath, err = GetRefPath(params.RefName, params.RefType)
|
||||||
@ -158,9 +159,9 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
|
|||||||
params.RefType, params.RefName, err)
|
params.RefType, params.RefName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
refOldValue, err = s.adapter.GetFullCommitID(ctx, repoPath, refPath)
|
refOldValue, err = s.git.GetFullCommitID(ctx, repoPath, refPath)
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
refOldValue = types.NilSHA
|
refOldValue = sha.Nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return MergeOutput{}, fmt.Errorf("failed to resolve %q: %w", refPath, err)
|
return MergeOutput{}, fmt.Errorf("failed to resolve %q: %w", refPath, err)
|
||||||
}
|
}
|
||||||
@ -178,17 +179,17 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
|
|||||||
|
|
||||||
// find the commit SHAs
|
// find the commit SHAs
|
||||||
|
|
||||||
baseCommitSHA, err := s.adapter.GetFullCommitID(ctx, repoPath, params.BaseBranch)
|
baseCommitSHA, err := s.git.GetFullCommitID(ctx, repoPath, params.BaseBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MergeOutput{}, fmt.Errorf("failed to get merge base branch commit SHA: %w", err)
|
return MergeOutput{}, fmt.Errorf("failed to get merge base branch commit SHA: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
headCommitSHA, err := s.adapter.GetFullCommitID(ctx, repoPath, params.HeadBranch)
|
headCommitSHA, err := s.git.GetFullCommitID(ctx, repoPath, params.HeadBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MergeOutput{}, fmt.Errorf("failed to get merge base branch commit SHA: %w", err)
|
return MergeOutput{}, fmt.Errorf("failed to get merge base branch commit SHA: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.HeadExpectedSHA != "" && params.HeadExpectedSHA != headCommitSHA {
|
if !params.HeadExpectedSHA.IsEmpty() && !params.HeadExpectedSHA.Equal(headCommitSHA) {
|
||||||
return MergeOutput{}, errors.PreconditionFailed(
|
return MergeOutput{}, errors.PreconditionFailed(
|
||||||
"head branch '%s' is on SHA '%s' which doesn't match expected SHA '%s'.",
|
"head branch '%s' is on SHA '%s' which doesn't match expected SHA '%s'.",
|
||||||
params.HeadBranch,
|
params.HeadBranch,
|
||||||
@ -196,25 +197,26 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
|
|||||||
params.HeadExpectedSHA)
|
params.HeadExpectedSHA)
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeBaseCommitSHA, _, err := s.adapter.GetMergeBase(ctx, repoPath, "origin", baseCommitSHA, headCommitSHA)
|
mergeBaseCommitSHA, _, err := s.git.GetMergeBase(ctx, repoPath, "origin",
|
||||||
|
baseCommitSHA.String(), headCommitSHA.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MergeOutput{}, fmt.Errorf("failed to get merge base: %w", err)
|
return MergeOutput{}, fmt.Errorf("failed to get merge base: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if headCommitSHA == mergeBaseCommitSHA {
|
if headCommitSHA.Equal(mergeBaseCommitSHA) {
|
||||||
return MergeOutput{}, errors.InvalidArgument("head branch doesn't contain any new commits.")
|
return MergeOutput{}, errors.InvalidArgument("head branch doesn't contain any new commits.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// find short stat and number of commits
|
// find short stat and number of commits
|
||||||
|
|
||||||
shortStat, err := s.adapter.DiffShortStat(ctx, repoPath, baseCommitSHA, headCommitSHA, true)
|
shortStat, err := s.git.DiffShortStat(ctx, repoPath, baseCommitSHA.String(), headCommitSHA.String(), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MergeOutput{}, errors.Internal(err,
|
return MergeOutput{}, errors.Internal(err,
|
||||||
"failed to find short stat between %s and %s", baseCommitSHA, headCommitSHA)
|
"failed to find short stat between %s and %s", baseCommitSHA, headCommitSHA)
|
||||||
}
|
}
|
||||||
changedFileCount := shortStat.Files
|
changedFileCount := shortStat.Files
|
||||||
|
|
||||||
commitCount, err := merge.CommitCount(ctx, repoPath, baseCommitSHA, headCommitSHA)
|
commitCount, err := merge.CommitCount(ctx, repoPath, baseCommitSHA.String(), headCommitSHA.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MergeOutput{}, fmt.Errorf("failed to find commit count for merge check: %w", err)
|
return MergeOutput{}, fmt.Errorf("failed to find commit count for merge check: %w", err)
|
||||||
}
|
}
|
||||||
@ -222,11 +224,11 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
|
|||||||
// handle simple merge check
|
// handle simple merge check
|
||||||
|
|
||||||
if params.RefType == enum.RefTypeUndefined {
|
if params.RefType == enum.RefTypeUndefined {
|
||||||
_, _, conflicts, err := merge.FindConflicts(ctx, repoPath, baseCommitSHA, headCommitSHA)
|
_, _, conflicts, err := merge.FindConflicts(ctx, repoPath, baseCommitSHA.String(), headCommitSHA.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MergeOutput{}, errors.Internal(err,
|
return MergeOutput{}, errors.Internal(err,
|
||||||
"Merge check failed to find conflicts between commits %s and %s",
|
"Merge check failed to find conflicts between commits %s and %s",
|
||||||
baseCommitSHA, headCommitSHA)
|
baseCommitSHA.String(), headCommitSHA.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("merged check completed")
|
log.Debug().Msg("merged check completed")
|
||||||
@ -235,7 +237,7 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
|
|||||||
BaseSHA: baseCommitSHA,
|
BaseSHA: baseCommitSHA,
|
||||||
HeadSHA: headCommitSHA,
|
HeadSHA: headCommitSHA,
|
||||||
MergeBaseSHA: mergeBaseCommitSHA,
|
MergeBaseSHA: mergeBaseCommitSHA,
|
||||||
MergeSHA: "",
|
MergeSHA: sha.None,
|
||||||
CommitCount: commitCount,
|
CommitCount: commitCount,
|
||||||
ChangedFileCount: changedFileCount,
|
ChangedFileCount: changedFileCount,
|
||||||
ConflictFiles: conflicts,
|
ConflictFiles: conflicts,
|
||||||
@ -246,10 +248,10 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
|
|||||||
|
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
|
|
||||||
committer := types.Signature{Identity: types.Identity(params.Actor), When: now}
|
committer := api.Signature{Identity: api.Identity(params.Actor), When: now}
|
||||||
|
|
||||||
if params.Committer != nil {
|
if params.Committer != nil {
|
||||||
committer.Identity = types.Identity(*params.Committer)
|
committer.Identity = api.Identity(*params.Committer)
|
||||||
}
|
}
|
||||||
if params.CommitterDate != nil {
|
if params.CommitterDate != nil {
|
||||||
committer.When = *params.CommitterDate
|
committer.When = *params.CommitterDate
|
||||||
@ -258,7 +260,7 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
|
|||||||
author := committer
|
author := committer
|
||||||
|
|
||||||
if params.Author != nil {
|
if params.Author != nil {
|
||||||
author.Identity = types.Identity(*params.Author)
|
author.Identity = api.Identity(*params.Author)
|
||||||
}
|
}
|
||||||
if params.AuthorDate != nil {
|
if params.AuthorDate != nil {
|
||||||
author.When = *params.AuthorDate
|
author.When = *params.AuthorDate
|
||||||
@ -288,7 +290,7 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
|
|||||||
BaseSHA: baseCommitSHA,
|
BaseSHA: baseCommitSHA,
|
||||||
HeadSHA: headCommitSHA,
|
HeadSHA: headCommitSHA,
|
||||||
MergeBaseSHA: mergeBaseCommitSHA,
|
MergeBaseSHA: mergeBaseCommitSHA,
|
||||||
MergeSHA: "",
|
MergeSHA: sha.None,
|
||||||
CommitCount: commitCount,
|
CommitCount: commitCount,
|
||||||
ChangedFileCount: changedFileCount,
|
ChangedFileCount: changedFileCount,
|
||||||
ConflictFiles: conflicts,
|
ConflictFiles: conflicts,
|
||||||
@ -299,7 +301,7 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
|
|||||||
|
|
||||||
log.Trace().Msg("merge completed - updating git reference")
|
log.Trace().Msg("merge completed - updating git reference")
|
||||||
|
|
||||||
err = s.adapter.UpdateRef(
|
err = s.git.UpdateRef(
|
||||||
ctx,
|
ctx,
|
||||||
params.EnvVars,
|
params.EnvVars,
|
||||||
repoPath,
|
repoPath,
|
||||||
@ -350,7 +352,7 @@ func (p *MergeBaseParams) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MergeBaseOutput struct {
|
type MergeBaseOutput struct {
|
||||||
MergeBaseSHA string
|
MergeBaseSHA sha.SHA
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) MergeBase(
|
func (s *Service) MergeBase(
|
||||||
@ -363,7 +365,7 @@ func (s *Service) MergeBase(
|
|||||||
|
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
|
|
||||||
result, _, err := s.adapter.GetMergeBase(ctx, repoPath, "", params.Ref1, params.Ref2)
|
result, _, err := s.git.GetMergeBase(ctx, repoPath, "", params.Ref1, params.Ref2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MergeBaseOutput{}, err
|
return MergeBaseOutput{}, err
|
||||||
}
|
}
|
||||||
@ -375,8 +377,8 @@ func (s *Service) MergeBase(
|
|||||||
|
|
||||||
type IsAncestorParams struct {
|
type IsAncestorParams struct {
|
||||||
ReadParams
|
ReadParams
|
||||||
AncestorCommitSHA string
|
AncestorCommitSHA sha.SHA
|
||||||
DescendantCommitSHA string
|
DescendantCommitSHA sha.SHA
|
||||||
}
|
}
|
||||||
|
|
||||||
type IsAncestorOutput struct {
|
type IsAncestorOutput struct {
|
||||||
@ -389,7 +391,7 @@ func (s *Service) IsAncestor(
|
|||||||
) (IsAncestorOutput, error) {
|
) (IsAncestorOutput, error) {
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
|
|
||||||
result, err := s.adapter.IsAncestor(ctx, repoPath, params.AncestorCommitSHA, params.DescendantCommitSHA)
|
result, err := s.git.IsAncestor(ctx, repoPath, params.AncestorCommitSHA, params.DescendantCommitSHA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return IsAncestorOutput{}, err
|
return IsAncestorOutput{}, err
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/adapter"
|
"github.com/harness/gitness/git/api"
|
||||||
|
"github.com/harness/gitness/git/sha"
|
||||||
"github.com/harness/gitness/git/sharedrepo"
|
"github.com/harness/gitness/git/sharedrepo"
|
||||||
"github.com/harness/gitness/git/types"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
@ -29,19 +29,19 @@ import (
|
|||||||
type Func func(
|
type Func func(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath, tmpDir string,
|
repoPath, tmpDir string,
|
||||||
author, committer *types.Signature,
|
author, committer *api.Signature,
|
||||||
message string,
|
message string,
|
||||||
mergeBaseSHA, targetSHA, sourceSHA string,
|
mergeBaseSHA, targetSHA, sourceSHA sha.SHA,
|
||||||
) (mergeSHA string, conflicts []string, err error)
|
) (mergeSHA sha.SHA, conflicts []string, err error)
|
||||||
|
|
||||||
// Merge merges two the commits (targetSHA and sourceSHA) using the Merge method.
|
// Merge merges two the commits (targetSHA and sourceSHA) using the Merge method.
|
||||||
func Merge(
|
func Merge(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath, tmpDir string,
|
repoPath, tmpDir string,
|
||||||
author, committer *types.Signature,
|
author, committer *api.Signature,
|
||||||
message string,
|
message string,
|
||||||
mergeBaseSHA, targetSHA, sourceSHA string,
|
mergeBaseSHA, targetSHA, sourceSHA sha.SHA,
|
||||||
) (mergeSHA string, conflicts []string, err error) {
|
) (mergeSHA sha.SHA, conflicts []string, err error) {
|
||||||
return mergeInternal(ctx,
|
return mergeInternal(ctx,
|
||||||
repoPath, tmpDir,
|
repoPath, tmpDir,
|
||||||
author, committer,
|
author, committer,
|
||||||
@ -54,10 +54,10 @@ func Merge(
|
|||||||
func Squash(
|
func Squash(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath, tmpDir string,
|
repoPath, tmpDir string,
|
||||||
author, committer *types.Signature,
|
author, committer *api.Signature,
|
||||||
message string,
|
message string,
|
||||||
mergeBaseSHA, targetSHA, sourceSHA string,
|
mergeBaseSHA, targetSHA, sourceSHA sha.SHA,
|
||||||
) (mergeSHA string, conflicts []string, err error) {
|
) (mergeSHA sha.SHA, conflicts []string, err error) {
|
||||||
return mergeInternal(ctx,
|
return mergeInternal(ctx,
|
||||||
repoPath, tmpDir,
|
repoPath, tmpDir,
|
||||||
author, committer,
|
author, committer,
|
||||||
@ -70,15 +70,15 @@ func Squash(
|
|||||||
func mergeInternal(
|
func mergeInternal(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath, tmpDir string,
|
repoPath, tmpDir string,
|
||||||
author, committer *types.Signature,
|
author, committer *api.Signature,
|
||||||
message string,
|
message string,
|
||||||
mergeBaseSHA, targetSHA, sourceSHA string,
|
mergeBaseSHA, targetSHA, sourceSHA sha.SHA,
|
||||||
squash bool,
|
squash bool,
|
||||||
) (mergeSHA string, conflicts []string, err error) {
|
) (mergeSHA sha.SHA, conflicts []string, err error) {
|
||||||
err = runInSharedRepo(ctx, tmpDir, repoPath, func(s *sharedrepo.SharedRepo) error {
|
err = runInSharedRepo(ctx, tmpDir, repoPath, func(s *sharedrepo.SharedRepo) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
var treeSHA string
|
var treeSHA sha.SHA
|
||||||
|
|
||||||
treeSHA, conflicts, err = s.MergeTree(ctx, mergeBaseSHA, targetSHA, sourceSHA)
|
treeSHA, conflicts, err = s.MergeTree(ctx, mergeBaseSHA, targetSHA, sourceSHA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -89,7 +89,7 @@ func mergeInternal(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
parents := make([]string, 0, 2)
|
parents := make([]sha.SHA, 0, 2)
|
||||||
parents = append(parents, targetSHA)
|
parents = append(parents, targetSHA)
|
||||||
if !squash {
|
if !squash {
|
||||||
parents = append(parents, sourceSHA)
|
parents = append(parents, sourceSHA)
|
||||||
@ -103,7 +103,7 @@ func mergeInternal(
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, fmt.Errorf("merge method=merge squash=%t: %w", squash, err)
|
return sha.None, nil, fmt.Errorf("merge method=merge squash=%t: %w", squash, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return mergeSHA, conflicts, nil
|
return mergeSHA, conflicts, nil
|
||||||
@ -115,10 +115,10 @@ func mergeInternal(
|
|||||||
func Rebase(
|
func Rebase(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath, tmpDir string,
|
repoPath, tmpDir string,
|
||||||
_, committer *types.Signature, // commit author isn't used here - it's copied from every commit
|
_, committer *api.Signature, // commit author isn't used here - it's copied from every commit
|
||||||
_ string, // commit message isn't used here
|
_ string, // commit message isn't used here
|
||||||
mergeBaseSHA, targetSHA, sourceSHA string,
|
mergeBaseSHA, targetSHA, sourceSHA sha.SHA,
|
||||||
) (mergeSHA string, conflicts []string, err error) {
|
) (mergeSHA sha.SHA, conflicts []string, err error) {
|
||||||
err = runInSharedRepo(ctx, tmpDir, repoPath, func(s *sharedrepo.SharedRepo) error {
|
err = runInSharedRepo(ctx, tmpDir, repoPath, func(s *sharedrepo.SharedRepo) error {
|
||||||
sourceSHAs, err := s.CommitSHAsForRebase(ctx, mergeBaseSHA, sourceSHA)
|
sourceSHAs, err := s.CommitSHAsForRebase(ctx, mergeBaseSHA, sourceSHA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -126,15 +126,15 @@ func Rebase(
|
|||||||
}
|
}
|
||||||
|
|
||||||
lastCommitSHA := targetSHA
|
lastCommitSHA := targetSHA
|
||||||
lastTreeSHA, err := s.GetTreeSHA(ctx, targetSHA)
|
lastTreeSHA, err := s.GetTreeSHA(ctx, targetSHA.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get tree sha for target: %w", err)
|
return fmt.Errorf("failed to get tree sha for target: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, commitSHA := range sourceSHAs {
|
for _, commitSHA := range sourceSHAs {
|
||||||
var treeSHA string
|
var treeSHA sha.SHA
|
||||||
|
|
||||||
commitInfo, err := adapter.GetCommit(ctx, s.Directory(), commitSHA, "")
|
commitInfo, err := api.GetCommit(ctx, s.Directory(), commitSHA.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get commit data in rebase merge: %w", err)
|
return fmt.Errorf("failed to get commit data in rebase merge: %w", err)
|
||||||
}
|
}
|
||||||
@ -146,7 +146,7 @@ func Rebase(
|
|||||||
message += "\n\n" + commitInfo.Message
|
message += "\n\n" + commitInfo.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeTreeMergeBaseSHA := ""
|
var mergeTreeMergeBaseSHA sha.SHA
|
||||||
if len(commitInfo.ParentSHAs) > 0 {
|
if len(commitInfo.ParentSHAs) > 0 {
|
||||||
// use parent of commit as merge base to only apply changes introduced by commit.
|
// use parent of commit as merge base to only apply changes introduced by commit.
|
||||||
// See example usage of when --merge-base was introduced:
|
// See example usage of when --merge-base was introduced:
|
||||||
@ -171,7 +171,7 @@ func Rebase(
|
|||||||
// 2. The changes of the commit already exist on the target branch.
|
// 2. The changes of the commit already exist on the target branch.
|
||||||
// Git's `git rebase` is dropping such commits on default (and so does Github)
|
// Git's `git rebase` is dropping such commits on default (and so does Github)
|
||||||
// https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---emptydropkeepask
|
// https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---emptydropkeepask
|
||||||
if treeSHA == lastTreeSHA {
|
if treeSHA.Equal(lastTreeSHA) {
|
||||||
log.Ctx(ctx).Debug().Msgf("skipping commit %s as it's empty after rebase", commitSHA)
|
log.Ctx(ctx).Debug().Msgf("skipping commit %s as it's empty after rebase", commitSHA)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -188,7 +188,7 @@ func Rebase(
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, fmt.Errorf("merge method=rebase: %w", err)
|
return sha.None, nil, fmt.Errorf("merge method=rebase: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return mergeSHA, conflicts, nil
|
return mergeSHA, conflicts, nil
|
||||||
|
@ -23,11 +23,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git/adapter"
|
"github.com/harness/gitness/git/api"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/sha"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/services/repository/files"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
@ -85,7 +83,7 @@ func (p *CommitFilesParams) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CommitFilesResponse struct {
|
type CommitFilesResponse struct {
|
||||||
CommitID string
|
CommitID sha.SHA
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocognit
|
//nolint:gocognit
|
||||||
@ -116,13 +114,6 @@ func (s *Service) CommitFiles(ctx context.Context, params *CommitFilesParams) (C
|
|||||||
|
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
||||||
|
|
||||||
log.Debug().Msg("open repository")
|
|
||||||
|
|
||||||
repo, err := s.adapter.OpenRepository(ctx, repoPath)
|
|
||||||
if err != nil {
|
|
||||||
return CommitFilesResponse{}, fmt.Errorf("CommitFiles: failed to open repo: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().Msg("check if empty")
|
log.Debug().Msg("check if empty")
|
||||||
|
|
||||||
// check if repo is empty
|
// check if repo is empty
|
||||||
@ -130,7 +121,7 @@ func (s *Service) CommitFiles(ctx context.Context, params *CommitFilesParams) (C
|
|||||||
// This can be an issue in case someone created a branch already in the repo (just default branch is missing).
|
// This can be an issue in case someone created a branch already in the repo (just default branch is missing).
|
||||||
// In that case the user can accidentally create separate git histories (which most likely is unintended).
|
// In that case the user can accidentally create separate git histories (which most likely is unintended).
|
||||||
// If the user wants to actually build a disconnected commit graph they can use the cli.
|
// If the user wants to actually build a disconnected commit graph they can use the cli.
|
||||||
isEmpty, err := s.adapter.HasBranches(ctx, repoPath)
|
isEmpty, err := s.git.HasBranches(ctx, repoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CommitFilesResponse{}, fmt.Errorf("CommitFiles: failed to determine if repository is empty: %w", err)
|
return CommitFilesResponse{}, fmt.Errorf("CommitFiles: failed to determine if repository is empty: %w", err)
|
||||||
}
|
}
|
||||||
@ -139,30 +130,30 @@ func (s *Service) CommitFiles(ctx context.Context, params *CommitFilesParams) (C
|
|||||||
|
|
||||||
// ensure input data is valid
|
// ensure input data is valid
|
||||||
// the commit will be nil for empty repositories
|
// the commit will be nil for empty repositories
|
||||||
commit, err := s.validateAndPrepareHeader(repo, isEmpty, params)
|
commit, err := s.validateAndPrepareHeader(ctx, repoPath, isEmpty, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CommitFilesResponse{}, err
|
return CommitFilesResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldCommitSHA string
|
var oldCommitSHA sha.SHA
|
||||||
if commit != nil {
|
if commit != nil {
|
||||||
oldCommitSHA = commit.ID.String()
|
oldCommitSHA = commit.SHA
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("create shared repo")
|
log.Debug().Msg("create shared repo")
|
||||||
|
|
||||||
newCommitSHA, err := func() (string, error) {
|
newCommitSHA, err := func() (sha.SHA, error) {
|
||||||
// Create a directory for the temporary shared repository.
|
// Create a directory for the temporary shared repository.
|
||||||
shared, err := s.adapter.SharedRepository(s.tmpDir, params.RepoUID, repo.Path)
|
shared, err := api.NewSharedRepo(s.git, s.tmpDir, params.RepoUID, repoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to create shared repository: %w", err)
|
return sha.None, fmt.Errorf("failed to create shared repository: %w", err)
|
||||||
}
|
}
|
||||||
defer shared.Close(ctx)
|
defer shared.Close(ctx)
|
||||||
|
|
||||||
// Create bare repository with alternates pointing to the original repository.
|
// Create bare repository with alternates pointing to the original repository.
|
||||||
err = shared.InitAsShared(ctx)
|
err = shared.InitAsShared(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to create temp repo with alternates: %w", err)
|
return sha.None, fmt.Errorf("failed to create temp repo with alternates: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msgf("prepare tree (empty: %t)", isEmpty)
|
log.Debug().Msgf("prepare tree (empty: %t)", isEmpty)
|
||||||
@ -171,15 +162,15 @@ func (s *Service) CommitFiles(ctx context.Context, params *CommitFilesParams) (C
|
|||||||
if isEmpty {
|
if isEmpty {
|
||||||
err = s.prepareTreeEmptyRepo(ctx, shared, params.Actions)
|
err = s.prepareTreeEmptyRepo(ctx, shared, params.Actions)
|
||||||
} else {
|
} else {
|
||||||
err = shared.SetIndex(ctx, oldCommitSHA)
|
err = shared.SetIndex(ctx, oldCommitSHA.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to set index to temp repo: %w", err)
|
return sha.None, fmt.Errorf("failed to set index to temp repo: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.prepareTree(ctx, shared, params.Actions, commit)
|
err = s.prepareTree(ctx, shared, params.Actions, commit)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to prepare tree: %w", err)
|
return sha.None, fmt.Errorf("failed to prepare tree: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("write tree")
|
log.Debug().Msg("write tree")
|
||||||
@ -187,7 +178,7 @@ func (s *Service) CommitFiles(ctx context.Context, params *CommitFilesParams) (C
|
|||||||
// Now write the tree
|
// Now write the tree
|
||||||
treeHash, err := shared.WriteTree(ctx)
|
treeHash, err := shared.WriteTree(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to write tree object: %w", err)
|
return sha.None, fmt.Errorf("failed to write tree object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
message := strings.TrimSpace(params.Title)
|
message := strings.TrimSpace(params.Title)
|
||||||
@ -201,11 +192,11 @@ func (s *Service) CommitFiles(ctx context.Context, params *CommitFilesParams) (C
|
|||||||
commitSHA, err := shared.CommitTreeWithDate(
|
commitSHA, err := shared.CommitTreeWithDate(
|
||||||
ctx,
|
ctx,
|
||||||
oldCommitSHA,
|
oldCommitSHA,
|
||||||
&types.Identity{
|
&api.Identity{
|
||||||
Name: author.Name,
|
Name: author.Name,
|
||||||
Email: author.Email,
|
Email: author.Email,
|
||||||
},
|
},
|
||||||
&types.Identity{
|
&api.Identity{
|
||||||
Name: committer.Name,
|
Name: committer.Name,
|
||||||
Email: committer.Email,
|
Email: committer.Email,
|
||||||
},
|
},
|
||||||
@ -216,12 +207,12 @@ func (s *Service) CommitFiles(ctx context.Context, params *CommitFilesParams) (C
|
|||||||
committerDate,
|
committerDate,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to commit the tree: %w", err)
|
return sha.None, fmt.Errorf("failed to commit the tree: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = shared.MoveObjects(ctx)
|
err = shared.MoveObjects(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to move git objects: %w", err)
|
return sha.None, fmt.Errorf("failed to move git objects: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return commitSHA, nil
|
return commitSHA, nil
|
||||||
@ -232,13 +223,13 @@ func (s *Service) CommitFiles(ctx context.Context, params *CommitFilesParams) (C
|
|||||||
|
|
||||||
log.Debug().Msg("update ref")
|
log.Debug().Msg("update ref")
|
||||||
|
|
||||||
branchRef := adapter.GetReferenceFromBranchName(params.Branch)
|
branchRef := api.GetReferenceFromBranchName(params.Branch)
|
||||||
if params.Branch != params.NewBranch {
|
if params.Branch != params.NewBranch {
|
||||||
// we are creating a new branch, rather than updating the existing one
|
// we are creating a new branch, rather than updating the existing one
|
||||||
oldCommitSHA = types.NilSHA
|
oldCommitSHA = sha.Nil
|
||||||
branchRef = adapter.GetReferenceFromBranchName(params.NewBranch)
|
branchRef = api.GetReferenceFromBranchName(params.NewBranch)
|
||||||
}
|
}
|
||||||
err = s.adapter.UpdateRef(
|
err = s.git.UpdateRef(
|
||||||
ctx,
|
ctx,
|
||||||
params.EnvVars,
|
params.EnvVars,
|
||||||
repoPath,
|
repoPath,
|
||||||
@ -252,23 +243,24 @@ func (s *Service) CommitFiles(ctx context.Context, params *CommitFilesParams) (C
|
|||||||
|
|
||||||
log.Debug().Msg("get commit")
|
log.Debug().Msg("get commit")
|
||||||
|
|
||||||
commit, err = repo.GetCommit(newCommitSHA)
|
commit, err = s.git.GetCommit(ctx, repoPath, newCommitSHA.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CommitFilesResponse{}, fmt.Errorf("failed to get commit for SHA %s: %w", newCommitSHA, err)
|
return CommitFilesResponse{}, fmt.Errorf("failed to get commit for SHA %s: %w",
|
||||||
|
newCommitSHA.String(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("done")
|
log.Debug().Msg("done")
|
||||||
|
|
||||||
return CommitFilesResponse{
|
return CommitFilesResponse{
|
||||||
CommitID: commit.ID.String(),
|
CommitID: commit.SHA,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) prepareTree(
|
func (s *Service) prepareTree(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
shared *adapter.SharedRepo,
|
shared *api.SharedRepo,
|
||||||
actions []CommitFileAction,
|
actions []CommitFileAction,
|
||||||
commit *git.Commit,
|
commit *api.Commit,
|
||||||
) error {
|
) error {
|
||||||
// execute all actions
|
// execute all actions
|
||||||
for i := range actions {
|
for i := range actions {
|
||||||
@ -282,7 +274,7 @@ func (s *Service) prepareTree(
|
|||||||
|
|
||||||
func (s *Service) prepareTreeEmptyRepo(
|
func (s *Service) prepareTreeEmptyRepo(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
shared *adapter.SharedRepo,
|
shared *api.SharedRepo,
|
||||||
actions []CommitFileAction,
|
actions []CommitFileAction,
|
||||||
) error {
|
) error {
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
@ -290,7 +282,7 @@ func (s *Service) prepareTreeEmptyRepo(
|
|||||||
return errors.PreconditionFailed("action not allowed on empty repository")
|
return errors.PreconditionFailed("action not allowed on empty repository")
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath := files.CleanUploadFileName(action.Path)
|
filePath := api.CleanUploadFileName(action.Path)
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return errors.InvalidArgument("invalid path")
|
return errors.InvalidArgument("invalid path")
|
||||||
}
|
}
|
||||||
@ -304,12 +296,13 @@ func (s *Service) prepareTreeEmptyRepo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) validateAndPrepareHeader(
|
func (s *Service) validateAndPrepareHeader(
|
||||||
repo *git.Repository,
|
ctx context.Context,
|
||||||
|
repoPath string,
|
||||||
isEmpty bool,
|
isEmpty bool,
|
||||||
params *CommitFilesParams,
|
params *CommitFilesParams,
|
||||||
) (*git.Commit, error) {
|
) (*api.Commit, error) {
|
||||||
if params.Branch == "" {
|
if params.Branch == "" {
|
||||||
defaultBranchRef, err := repo.GetDefaultBranch()
|
defaultBranchRef, err := s.git.GetDefaultBranch(ctx, repoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get default branch: %w", err)
|
return nil, fmt.Errorf("failed to get default branch: %w", err)
|
||||||
}
|
}
|
||||||
@ -330,37 +323,32 @@ func (s *Service) validateAndPrepareHeader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ensure source branch exists
|
// ensure source branch exists
|
||||||
branch, err := repo.GetBranch(params.Branch)
|
branch, err := s.git.GetBranch(ctx, repoPath, params.Branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get source branch '%s': %w", params.Branch, err)
|
return nil, fmt.Errorf("failed to get source branch '%s': %w", params.Branch, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure new branch doesn't exist yet (if new branch creation was requested)
|
// ensure new branch doesn't exist yet (if new branch creation was requested)
|
||||||
if params.Branch != params.NewBranch {
|
if params.Branch != params.NewBranch {
|
||||||
existingBranch, err := repo.GetBranch(params.NewBranch)
|
existingBranch, err := s.git.GetBranch(ctx, repoPath, params.NewBranch)
|
||||||
if existingBranch != nil {
|
if existingBranch != nil {
|
||||||
return nil, errors.Conflict("branch %s already exists", existingBranch.Name)
|
return nil, errors.Conflict("branch %s already exists", existingBranch.Name)
|
||||||
}
|
}
|
||||||
if err != nil && !git.IsErrBranchNotExist(err) {
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
return nil, fmt.Errorf("failed to create new branch '%s': %w", params.NewBranch, err)
|
return nil, fmt.Errorf("failed to create new branch '%s': %w", params.NewBranch, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commit, err := branch.GetCommit()
|
return branch.Commit, nil
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get branch commit: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return commit, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) processAction(
|
func (s *Service) processAction(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
shared SharedRepo,
|
shared *api.SharedRepo,
|
||||||
action *CommitFileAction,
|
action *CommitFileAction,
|
||||||
commit *git.Commit,
|
commit *api.Commit,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
filePath := files.CleanUploadFileName(action.Path)
|
filePath := api.CleanUploadFileName(action.Path)
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return errors.InvalidArgument("path cannot be empty")
|
return errors.InvalidArgument("path cannot be empty")
|
||||||
}
|
}
|
||||||
@ -379,11 +367,11 @@ func (s *Service) processAction(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFile(ctx context.Context, repo SharedRepo, commit *git.Commit,
|
func createFile(ctx context.Context, repo *api.SharedRepo, commit *api.Commit,
|
||||||
filePath, mode string, payload []byte) error {
|
filePath, mode string, payload []byte) error {
|
||||||
// only check path availability if a source commit is available (empty repo won't have such a commit)
|
// only check path availability if a source commit is available (empty repo won't have such a commit)
|
||||||
if commit != nil {
|
if commit != nil {
|
||||||
if err := checkPathAvailability(commit, filePath, true); err != nil {
|
if err := checkPathAvailability(ctx, repo, commit, filePath, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -394,16 +382,23 @@ func createFile(ctx context.Context, repo SharedRepo, commit *git.Commit,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the object to the index
|
// Add the object to the index
|
||||||
if err = repo.AddObjectToIndex(ctx, mode, hash, filePath); err != nil {
|
if err = repo.AddObjectToIndex(ctx, mode, hash.String(), filePath); err != nil {
|
||||||
return fmt.Errorf("createFile: error creating object: %w", err)
|
return fmt.Errorf("createFile: error creating object: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateFile(ctx context.Context, repo SharedRepo, commit *git.Commit, filePath, sha,
|
func updateFile(
|
||||||
mode string, payload []byte) error {
|
ctx context.Context,
|
||||||
|
repo *api.SharedRepo,
|
||||||
|
commit *api.Commit,
|
||||||
|
filePath string,
|
||||||
|
sha string,
|
||||||
|
mode string,
|
||||||
|
payload []byte,
|
||||||
|
) error {
|
||||||
// get file mode from existing file (default unless executable)
|
// get file mode from existing file (default unless executable)
|
||||||
entry, err := getFileEntry(commit, sha, filePath)
|
entry, err := getFileEntry(ctx, repo, commit, sha, filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -416,27 +411,34 @@ func updateFile(ctx context.Context, repo SharedRepo, commit *git.Commit, filePa
|
|||||||
return fmt.Errorf("updateFile: error hashing object: %w", err)
|
return fmt.Errorf("updateFile: error hashing object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = repo.AddObjectToIndex(ctx, mode, hash, filePath); err != nil {
|
if err = repo.AddObjectToIndex(ctx, mode, hash.String(), filePath); err != nil {
|
||||||
return fmt.Errorf("updateFile: error updating object: %w", err)
|
return fmt.Errorf("updateFile: error updating object: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveFile(ctx context.Context, repo SharedRepo, commit *git.Commit,
|
func moveFile(
|
||||||
filePath, sha, mode string, payload []byte) error {
|
ctx context.Context,
|
||||||
|
repo *api.SharedRepo,
|
||||||
|
commit *api.Commit,
|
||||||
|
filePath string,
|
||||||
|
sha string,
|
||||||
|
mode string,
|
||||||
|
payload []byte,
|
||||||
|
) error {
|
||||||
newPath, newContent, err := parseMovePayload(payload)
|
newPath, newContent, err := parseMovePayload(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure file exists and matches SHA
|
// ensure file exists and matches SHA
|
||||||
entry, err := getFileEntry(commit, sha, filePath)
|
entry, err := getFileEntry(ctx, repo, commit, sha, filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure new path is available
|
// ensure new path is available
|
||||||
if err = checkPathAvailability(commit, newPath, false); err != nil {
|
if err = checkPathAvailability(ctx, repo, commit, newPath, false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,14 +450,14 @@ func moveFile(ctx context.Context, repo SharedRepo, commit *git.Commit,
|
|||||||
return fmt.Errorf("moveFile: error hashing object: %w", err)
|
return fmt.Errorf("moveFile: error hashing object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileHash = hash
|
fileHash = hash.String()
|
||||||
fileMode = mode
|
fileMode = mode
|
||||||
if entry.IsExecutable() {
|
if entry.IsExecutable() {
|
||||||
fileMode = "100755"
|
fileMode = "100755"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fileHash = entry.ID.String()
|
fileHash = entry.SHA.String()
|
||||||
fileMode = entry.Mode().String()
|
fileMode = entry.Mode.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = repo.AddObjectToIndex(ctx, fileMode, fileHash, newPath); err != nil {
|
if err = repo.AddObjectToIndex(ctx, fileMode, fileHash, newPath); err != nil {
|
||||||
@ -468,7 +470,7 @@ func moveFile(ctx context.Context, repo SharedRepo, commit *git.Commit,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteFile(ctx context.Context, repo SharedRepo, filePath string) error {
|
func deleteFile(ctx context.Context, repo *api.SharedRepo, filePath string) error {
|
||||||
filesInIndex, err := repo.LsFiles(ctx, filePath)
|
filesInIndex, err := repo.LsFiles(ctx, filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("deleteFile: listing files error: %w", err)
|
return fmt.Errorf("deleteFile: listing files error: %w", err)
|
||||||
@ -484,12 +486,14 @@ func deleteFile(ctx context.Context, repo SharedRepo, filePath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getFileEntry(
|
func getFileEntry(
|
||||||
commit *git.Commit,
|
ctx context.Context,
|
||||||
|
repo *api.SharedRepo,
|
||||||
|
commit *api.Commit,
|
||||||
sha string,
|
sha string,
|
||||||
path string,
|
path string,
|
||||||
) (*git.TreeEntry, error) {
|
) (*api.TreeNode, error) {
|
||||||
entry, err := commit.GetTreeEntryByPath(path)
|
entry, err := repo.GetTreeNode(ctx, commit.SHA.String(), path)
|
||||||
if git.IsErrNotExist(err) {
|
if errors.IsNotFound(err) {
|
||||||
return nil, errors.NotFound("path %s not found", path)
|
return nil, errors.NotFound("path %s not found", path)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -497,9 +501,9 @@ func getFileEntry(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error
|
// If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error
|
||||||
if sha != "" && sha != entry.ID.String() {
|
if sha != "" && sha != entry.SHA.String() {
|
||||||
return nil, errors.InvalidArgument("sha does not match for path %s [given: %s, expected: %s]",
|
return nil, errors.InvalidArgument("sha does not match for path %s [given: %s, expected: %s]",
|
||||||
path, sha, entry.ID.String())
|
path, sha, entry.SHA)
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry, nil
|
return entry, nil
|
||||||
@ -510,14 +514,20 @@ func getFileEntry(
|
|||||||
// sure no parts of the path are existing files or links except for the last
|
// sure no parts of the path are existing files or links except for the last
|
||||||
// item in the path which is the file name, and that shouldn't exist IF it is
|
// item in the path which is the file name, and that shouldn't exist IF it is
|
||||||
// a new file OR is being moved to a new path.
|
// a new file OR is being moved to a new path.
|
||||||
func checkPathAvailability(commit *git.Commit, filePath string, isNewFile bool) error {
|
func checkPathAvailability(
|
||||||
|
ctx context.Context,
|
||||||
|
repo *api.SharedRepo,
|
||||||
|
commit *api.Commit,
|
||||||
|
filePath string,
|
||||||
|
isNewFile bool,
|
||||||
|
) error {
|
||||||
parts := strings.Split(filePath, "/")
|
parts := strings.Split(filePath, "/")
|
||||||
subTreePath := ""
|
subTreePath := ""
|
||||||
for index, part := range parts {
|
for index, part := range parts {
|
||||||
subTreePath = path.Join(subTreePath, part)
|
subTreePath = path.Join(subTreePath, part)
|
||||||
entry, err := commit.GetTreeEntryByPath(subTreePath)
|
entry, err := repo.GetTreeNode(ctx, commit.SHA.String(), subTreePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if git.IsErrNotExist(err) {
|
if errors.IsNotFound(err) {
|
||||||
// Means there is no item with that name, so we're good
|
// Means there is no item with that name, so we're good
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -530,8 +540,8 @@ func checkPathAvailability(commit *git.Commit, filePath string, isNewFile bool)
|
|||||||
subTreePath)
|
subTreePath)
|
||||||
}
|
}
|
||||||
case entry.IsLink():
|
case entry.IsLink():
|
||||||
return fmt.Errorf("a symbolic link %w where you're trying to create a subdirectory [path: %s]",
|
return errors.Conflict("a symbolic link already exist where you're trying to create a subdirectory [path: %s]",
|
||||||
types.ErrAlreadyExists, subTreePath)
|
subTreePath)
|
||||||
case entry.IsDir():
|
case entry.IsDir():
|
||||||
return errors.Conflict("a directory already exists where you're trying to create a subdirectory [path: %s]",
|
return errors.Conflict("a directory already exists where you're trying to create a subdirectory [path: %s]",
|
||||||
subTreePath)
|
subTreePath)
|
||||||
@ -554,9 +564,9 @@ func parseMovePayload(payload []byte) (string, []byte, error) {
|
|||||||
newContent = payload[filePathEnd+1:]
|
newContent = payload[filePathEnd+1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
newPath = files.CleanUploadFileName(newPath)
|
newPath = api.CleanUploadFileName(newPath)
|
||||||
if newPath == "" {
|
if newPath == "" {
|
||||||
return "", nil, types.ErrInvalidPath
|
return "", nil, api.ErrInvalidPath
|
||||||
}
|
}
|
||||||
|
|
||||||
return newPath, newContent, nil
|
return newPath, newContent, nil
|
||||||
|
@ -18,34 +18,48 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DiffFileHeader struct {
|
||||||
|
OldFileName string
|
||||||
|
NewFileName string
|
||||||
|
Extensions map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiffCutParams struct {
|
||||||
|
LineStart int
|
||||||
|
LineStartNew bool
|
||||||
|
LineEnd int
|
||||||
|
LineEndNew bool
|
||||||
|
BeforeLines int
|
||||||
|
AfterLines int
|
||||||
|
LineLimit int
|
||||||
|
}
|
||||||
|
|
||||||
// DiffCut parses git diff output that should consist of a single hunk
|
// DiffCut parses git diff output that should consist of a single hunk
|
||||||
// (usually generated with large value passed to the "--unified" parameter)
|
// (usually generated with large value passed to the "--unified" parameter)
|
||||||
// and returns lines specified with the parameters.
|
// and returns lines specified with the parameters.
|
||||||
//
|
//
|
||||||
//nolint:funlen,gocognit,nestif,gocognit,gocyclo,cyclop // it's actually very readable
|
//nolint:funlen,gocognit,nestif,gocognit,gocyclo,cyclop // it's actually very readable
|
||||||
func DiffCut(r io.Reader, params types.DiffCutParams) (types.HunkHeader, types.Hunk, error) {
|
func DiffCut(r io.Reader, params DiffCutParams) (HunkHeader, Hunk, error) {
|
||||||
scanner := bufio.NewScanner(r)
|
scanner := bufio.NewScanner(r)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var hunkHeader types.HunkHeader
|
var hunkHeader HunkHeader
|
||||||
|
|
||||||
if _, err = scanFileHeader(scanner); err != nil {
|
if _, err = scanFileHeader(scanner); err != nil {
|
||||||
return types.HunkHeader{}, types.Hunk{}, err
|
return HunkHeader{}, Hunk{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if hunkHeader, err = scanHunkHeader(scanner); err != nil {
|
if hunkHeader, err = scanHunkHeader(scanner); err != nil {
|
||||||
return types.HunkHeader{}, types.Hunk{}, err
|
return HunkHeader{}, Hunk{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
currentOldLine := hunkHeader.OldLine
|
currentOldLine := hunkHeader.OldLine
|
||||||
currentNewLine := hunkHeader.NewLine
|
currentNewLine := hunkHeader.NewLine
|
||||||
|
|
||||||
var inCut bool
|
var inCut bool
|
||||||
var diffCutHeader types.HunkHeader
|
var diffCutHeader HunkHeader
|
||||||
var diffCut []string
|
var diffCut []string
|
||||||
|
|
||||||
linesBeforeBuf := newStrCircBuf(params.BeforeLines)
|
linesBeforeBuf := newStrCircBuf(params.BeforeLines)
|
||||||
@ -61,7 +75,7 @@ func DiffCut(r io.Reader, params types.DiffCutParams) (types.HunkHeader, types.H
|
|||||||
|
|
||||||
line, action, err = scanHunkLine(scanner)
|
line, action, err = scanHunkLine(scanner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.HunkHeader{}, types.Hunk{}, err
|
return HunkHeader{}, Hunk{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if line == "" {
|
if line == "" {
|
||||||
@ -103,7 +117,7 @@ func DiffCut(r io.Reader, params types.DiffCutParams) (types.HunkHeader, types.H
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !inCut {
|
if !inCut {
|
||||||
return types.HunkHeader{}, types.Hunk{}, types.ErrHunkNotFound
|
return HunkHeader{}, Hunk{}, ErrHunkNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -116,7 +130,7 @@ func DiffCut(r io.Reader, params types.DiffCutParams) (types.HunkHeader, types.H
|
|||||||
for i := 0; i < params.AfterLines; i++ {
|
for i := 0; i < params.AfterLines; i++ {
|
||||||
line, _, err := scanHunkLine(scanner)
|
line, _, err := scanHunkLine(scanner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.HunkHeader{}, types.Hunk{}, err
|
return HunkHeader{}, Hunk{}, err
|
||||||
}
|
}
|
||||||
if line == "" {
|
if line == "" {
|
||||||
break
|
break
|
||||||
@ -149,14 +163,14 @@ func DiffCut(r io.Reader, params types.DiffCutParams) (types.HunkHeader, types.H
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return diffCutHeader, types.Hunk{
|
return diffCutHeader, Hunk{
|
||||||
HunkHeader: diffCutHeaderLines,
|
HunkHeader: diffCutHeaderLines,
|
||||||
Lines: concat(linesBefore, diffCut, linesAfter),
|
Lines: concat(linesBefore, diffCut, linesAfter),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// scanFileHeader keeps reading lines until file header line is read.
|
// scanFileHeader keeps reading lines until file header line is read.
|
||||||
func scanFileHeader(scan *bufio.Scanner) (types.DiffFileHeader, error) {
|
func scanFileHeader(scan *bufio.Scanner) (DiffFileHeader, error) {
|
||||||
for scan.Scan() {
|
for scan.Scan() {
|
||||||
line := scan.Text()
|
line := scan.Text()
|
||||||
if h, ok := ParseDiffFileHeader(line); ok {
|
if h, ok := ParseDiffFileHeader(line); ok {
|
||||||
@ -165,14 +179,14 @@ func scanFileHeader(scan *bufio.Scanner) (types.DiffFileHeader, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := scan.Err(); err != nil {
|
if err := scan.Err(); err != nil {
|
||||||
return types.DiffFileHeader{}, err
|
return DiffFileHeader{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return types.DiffFileHeader{}, types.ErrHunkNotFound
|
return DiffFileHeader{}, ErrHunkNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// scanHunkHeader keeps reading lines until hunk header line is read.
|
// scanHunkHeader keeps reading lines until hunk header line is read.
|
||||||
func scanHunkHeader(scan *bufio.Scanner) (types.HunkHeader, error) {
|
func scanHunkHeader(scan *bufio.Scanner) (HunkHeader, error) {
|
||||||
for scan.Scan() {
|
for scan.Scan() {
|
||||||
line := scan.Text()
|
line := scan.Text()
|
||||||
if h, ok := ParseDiffHunkHeader(line); ok {
|
if h, ok := ParseDiffHunkHeader(line); ok {
|
||||||
@ -181,10 +195,10 @@ func scanHunkHeader(scan *bufio.Scanner) (types.HunkHeader, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := scan.Err(); err != nil {
|
if err := scan.Err(); err != nil {
|
||||||
return types.HunkHeader{}, err
|
return HunkHeader{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return types.HunkHeader{}, types.ErrHunkNotFound
|
return HunkHeader{}, ErrHunkNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
type diffAction byte
|
type diffAction byte
|
||||||
@ -206,7 +220,7 @@ again:
|
|||||||
|
|
||||||
line = scan.Text()
|
line = scan.Text()
|
||||||
if line == "" {
|
if line == "" {
|
||||||
err = types.ErrHunkNotFound // should not happen: empty line in diff output
|
err = ErrHunkNotFound // should not happen: empty line in diff output
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,8 +18,6 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:gocognit // it's a unit test!!!
|
//nolint:gocognit // it's a unit test!!!
|
||||||
@ -49,14 +47,14 @@ func TestDiffCut(t *testing.T) {
|
|||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
params types.DiffCutParams
|
params DiffCutParams
|
||||||
expCutHeader string
|
expCutHeader string
|
||||||
expCut []string
|
expCut []string
|
||||||
expError error
|
expError error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "at-'+6,7,8':new",
|
name: "at-'+6,7,8':new",
|
||||||
params: types.DiffCutParams{
|
params: DiffCutParams{
|
||||||
LineStart: 7, LineStartNew: true,
|
LineStart: 7, LineStartNew: true,
|
||||||
LineEnd: 7, LineEndNew: true,
|
LineEnd: 7, LineEndNew: true,
|
||||||
BeforeLines: 0, AfterLines: 0,
|
BeforeLines: 0, AfterLines: 0,
|
||||||
@ -68,7 +66,7 @@ func TestDiffCut(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "at-'+6,7,8':new-with-lines-around",
|
name: "at-'+6,7,8':new-with-lines-around",
|
||||||
params: types.DiffCutParams{
|
params: DiffCutParams{
|
||||||
LineStart: 7, LineStartNew: true,
|
LineStart: 7, LineStartNew: true,
|
||||||
LineEnd: 7, LineEndNew: true,
|
LineEnd: 7, LineEndNew: true,
|
||||||
BeforeLines: 1, AfterLines: 2,
|
BeforeLines: 1, AfterLines: 2,
|
||||||
@ -80,7 +78,7 @@ func TestDiffCut(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "at-'+0':new-with-lines-around",
|
name: "at-'+0':new-with-lines-around",
|
||||||
params: types.DiffCutParams{
|
params: DiffCutParams{
|
||||||
LineStart: 1, LineStartNew: true,
|
LineStart: 1, LineStartNew: true,
|
||||||
LineEnd: 1, LineEndNew: true,
|
LineEnd: 1, LineEndNew: true,
|
||||||
BeforeLines: 3, AfterLines: 3,
|
BeforeLines: 3, AfterLines: 3,
|
||||||
@ -92,7 +90,7 @@ func TestDiffCut(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "at-'-13':one-with-lines-around",
|
name: "at-'-13':one-with-lines-around",
|
||||||
params: types.DiffCutParams{
|
params: DiffCutParams{
|
||||||
LineStart: 13, LineStartNew: false,
|
LineStart: 13, LineStartNew: false,
|
||||||
LineEnd: 13, LineEndNew: false,
|
LineEnd: 13, LineEndNew: false,
|
||||||
BeforeLines: 1, AfterLines: 1,
|
BeforeLines: 1, AfterLines: 1,
|
||||||
@ -104,7 +102,7 @@ func TestDiffCut(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "at-'-13':mixed",
|
name: "at-'-13':mixed",
|
||||||
params: types.DiffCutParams{
|
params: DiffCutParams{
|
||||||
LineStart: 7, LineStartNew: false,
|
LineStart: 7, LineStartNew: false,
|
||||||
LineEnd: 7, LineEndNew: true,
|
LineEnd: 7, LineEndNew: true,
|
||||||
BeforeLines: 0, AfterLines: 0,
|
BeforeLines: 0, AfterLines: 0,
|
||||||
@ -167,7 +165,7 @@ index 541cb64f..047d7ee2 100644
|
|||||||
|
|
||||||
hh, h, err := DiffCut(
|
hh, h, err := DiffCut(
|
||||||
strings.NewReader(input),
|
strings.NewReader(input),
|
||||||
types.DiffCutParams{
|
DiffCutParams{
|
||||||
LineStart: 3,
|
LineStart: 3,
|
||||||
LineStartNew: true,
|
LineStartNew: true,
|
||||||
LineEnd: 3,
|
LineEnd: 3,
|
||||||
@ -182,13 +180,13 @@ index 541cb64f..047d7ee2 100644
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedHH := types.HunkHeader{OldLine: 2, OldSpan: 0, NewLine: 3, NewSpan: 1}
|
expectedHH := HunkHeader{OldLine: 2, OldSpan: 0, NewLine: 3, NewSpan: 1}
|
||||||
if expectedHH != hh {
|
if expectedHH != hh {
|
||||||
t.Errorf("expected hunk header: %+v, but got: %+v", expectedHH, hh)
|
t.Errorf("expected hunk header: %+v, but got: %+v", expectedHH, hh)
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedHunkLines := types.Hunk{
|
expectedHunkLines := Hunk{
|
||||||
HunkHeader: types.HunkHeader{OldLine: 2, OldSpan: 0, NewLine: 2, NewSpan: 2},
|
HunkHeader: HunkHeader{OldLine: 2, OldSpan: 0, NewLine: 2, NewSpan: 2},
|
||||||
Lines: []string{"+456", "+789"},
|
Lines: []string{"+456", "+789"},
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(expectedHunkLines, h) {
|
if !reflect.DeepEqual(expectedHunkLines, h) {
|
||||||
@ -210,7 +208,7 @@ index af7864ba..541cb64f 100644
|
|||||||
`
|
`
|
||||||
hh, h, err := DiffCut(
|
hh, h, err := DiffCut(
|
||||||
strings.NewReader(input),
|
strings.NewReader(input),
|
||||||
types.DiffCutParams{
|
DiffCutParams{
|
||||||
LineStart: 1,
|
LineStart: 1,
|
||||||
LineStartNew: true,
|
LineStartNew: true,
|
||||||
LineEnd: 1,
|
LineEnd: 1,
|
||||||
@ -225,13 +223,13 @@ index af7864ba..541cb64f 100644
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedHH := types.HunkHeader{OldLine: 1, OldSpan: 3, NewLine: 1, NewSpan: 1}
|
expectedHH := HunkHeader{OldLine: 1, OldSpan: 3, NewLine: 1, NewSpan: 1}
|
||||||
if expectedHH != hh {
|
if expectedHH != hh {
|
||||||
t.Errorf("expected hunk header: %+v, but got: %+v", expectedHH, hh)
|
t.Errorf("expected hunk header: %+v, but got: %+v", expectedHH, hh)
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedHunkLines := types.Hunk{
|
expectedHunkLines := Hunk{
|
||||||
HunkHeader: types.HunkHeader{OldLine: 1, OldSpan: 3, NewLine: 1, NewSpan: 1},
|
HunkHeader: HunkHeader{OldLine: 1, OldSpan: 3, NewLine: 1, NewSpan: 1},
|
||||||
Lines: []string{"-123", "-456", "-789", "+test"},
|
Lines: []string{"-123", "-456", "-789", "+test"},
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(expectedHunkLines, h) {
|
if !reflect.DeepEqual(expectedHunkLines, h) {
|
||||||
|
@ -20,18 +20,22 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/enum"
|
"github.com/harness/gitness/git/enum"
|
||||||
"github.com/harness/gitness/git/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DiffFileHunkHeaders struct {
|
||||||
|
FileHeader DiffFileHeader
|
||||||
|
HunksHeaders []HunkHeader
|
||||||
|
}
|
||||||
|
|
||||||
var regExpDiffFileHeader = regexp.MustCompile(`^diff --git a/(.+) b/(.+)$`)
|
var regExpDiffFileHeader = regexp.MustCompile(`^diff --git a/(.+) b/(.+)$`)
|
||||||
|
|
||||||
func ParseDiffFileHeader(line string) (types.DiffFileHeader, bool) {
|
func ParseDiffFileHeader(line string) (DiffFileHeader, bool) {
|
||||||
groups := regExpDiffFileHeader.FindStringSubmatch(line)
|
groups := regExpDiffFileHeader.FindStringSubmatch(line)
|
||||||
if groups == nil {
|
if groups == nil {
|
||||||
return types.DiffFileHeader{}, false
|
return DiffFileHeader{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return types.DiffFileHeader{
|
return DiffFileHeader{
|
||||||
OldFileName: groups[1],
|
OldFileName: groups[1],
|
||||||
NewFileName: groups[2],
|
NewFileName: groups[2],
|
||||||
Extensions: map[string]string{},
|
Extensions: map[string]string{},
|
||||||
@ -64,11 +68,11 @@ func ParseDiffFileExtendedHeader(line string) (string, string) {
|
|||||||
|
|
||||||
// GetHunkHeaders parses git diff output and returns all diff headers for all files.
|
// GetHunkHeaders parses git diff output and returns all diff headers for all files.
|
||||||
// See for documentation: https://git-scm.com/docs/git-diff#generate_patch_text_with_p
|
// See for documentation: https://git-scm.com/docs/git-diff#generate_patch_text_with_p
|
||||||
func GetHunkHeaders(r io.Reader) ([]*types.DiffFileHunkHeaders, error) {
|
func GetHunkHeaders(r io.Reader) ([]*DiffFileHunkHeaders, error) {
|
||||||
scanner := bufio.NewScanner(r)
|
scanner := bufio.NewScanner(r)
|
||||||
|
|
||||||
var currentFile *types.DiffFileHunkHeaders
|
var currentFile *DiffFileHunkHeaders
|
||||||
var result []*types.DiffFileHunkHeaders
|
var result []*DiffFileHunkHeaders
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
@ -77,7 +81,7 @@ func GetHunkHeaders(r io.Reader) ([]*types.DiffFileHunkHeaders, error) {
|
|||||||
if currentFile != nil {
|
if currentFile != nil {
|
||||||
result = append(result, currentFile)
|
result = append(result, currentFile)
|
||||||
}
|
}
|
||||||
currentFile = &types.DiffFileHunkHeaders{
|
currentFile = &DiffFileHunkHeaders{
|
||||||
FileHeader: h,
|
FileHeader: h,
|
||||||
HunksHeaders: nil,
|
HunksHeaders: nil,
|
||||||
}
|
}
|
||||||
@ -87,7 +91,7 @@ func GetHunkHeaders(r io.Reader) ([]*types.DiffFileHunkHeaders, error) {
|
|||||||
|
|
||||||
if currentFile == nil {
|
if currentFile == nil {
|
||||||
// should not happen: we reached the hunk header without first finding the file header.
|
// should not happen: we reached the hunk header without first finding the file header.
|
||||||
return nil, types.ErrHunkNotFound
|
return nil, ErrHunkNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
if h, ok := ParseDiffHunkHeader(line); ok {
|
if h, ok := ParseDiffHunkHeader(line); ok {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user