diff --git a/modules/actions/task_state.go b/modules/actions/task_state.go index 31a74be3fd..1f36e021a5 100644 --- a/modules/actions/task_state.go +++ b/modules/actions/task_state.go @@ -18,8 +18,32 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep { return fullStepsOfEmptySteps(task) } - firstStep := task.Steps[0] + // firstStep is the first step that has run or running, not include preStep. + // For example, + // 1. preStep(Success) -> step1(Success) -> step2(Running) -> step3(Waiting) -> postStep(Waiting): firstStep is step1. + // 2. preStep(Success) -> step1(Skipped) -> step2(Success) -> postStep(Success): firstStep is step2. + // 3. preStep(Success) -> step1(Running) -> step2(Waiting) -> postStep(Waiting): firstStep is step1. + // 4. preStep(Success) -> step1(Skipped) -> step2(Skipped) -> postStep(Skipped): firstStep is nil. + // 5. preStep(Success) -> step1(Cancelled) -> step2(Cancelled) -> postStep(Cancelled): firstStep is nil. + var firstStep *actions_model.ActionTaskStep + // lastHasRunStep is the last step that has run. + // For example, + // 1. preStep(Success) -> step1(Success) -> step2(Running) -> step3(Waiting) -> postStep(Waiting): lastHasRunStep is step1. + // 2. preStep(Success) -> step1(Success) -> step2(Success) -> step3(Success) -> postStep(Success): lastHasRunStep is step3. + // 3. preStep(Success) -> step1(Success) -> step2(Failure) -> step3 -> postStep(Waiting): lastHasRunStep is step2. + // So its Stopped is the Started of postStep when there are no more steps to run. + var lastHasRunStep *actions_model.ActionTaskStep + var logIndex int64 + for _, step := range task.Steps { + if firstStep == nil && (step.Status.HasRun() || step.Status.IsRunning()) { + firstStep = step + } + if step.Status.HasRun() { + lastHasRunStep = step + } + logIndex += step.LogLength + } preStep := &actions_model.ActionTaskStep{ Name: preStepName, @@ -28,32 +52,17 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep { Status: actions_model.StatusRunning, } - if firstStep.Status.HasRun() || firstStep.Status.IsRunning() { + // No step has run or is running, so preStep is equal to the task + if firstStep == nil { + preStep.Stopped = task.Stopped + preStep.Status = task.Status + } else { preStep.LogLength = firstStep.LogIndex preStep.Stopped = firstStep.Started preStep.Status = actions_model.StatusSuccess - } else if task.Status.IsDone() { - preStep.Stopped = task.Stopped - preStep.Status = actions_model.StatusFailure - if task.Status.IsSkipped() { - preStep.Status = actions_model.StatusSkipped - } } logIndex += preStep.LogLength - // lastHasRunStep is the last step that has run. - // For example, - // 1. preStep(Success) -> step1(Success) -> step2(Running) -> step3(Waiting) -> postStep(Waiting): lastHasRunStep is step1. - // 2. preStep(Success) -> step1(Success) -> step2(Success) -> step3(Success) -> postStep(Success): lastHasRunStep is step3. - // 3. preStep(Success) -> step1(Success) -> step2(Failure) -> step3 -> postStep(Waiting): lastHasRunStep is step2. - // So its Stopped is the Started of postStep when there are no more steps to run. - var lastHasRunStep *actions_model.ActionTaskStep - for _, step := range task.Steps { - if step.Status.HasRun() { - lastHasRunStep = step - } - logIndex += step.LogLength - } if lastHasRunStep == nil { lastHasRunStep = preStep } diff --git a/modules/actions/task_state_test.go b/modules/actions/task_state_test.go index 28213d781b..ff0fd57195 100644 --- a/modules/actions/task_state_test.go +++ b/modules/actions/task_state_test.go @@ -137,6 +137,25 @@ func TestFullSteps(t *testing.T) { {Name: postStepName, Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, }, }, + { + name: "first step is skipped", + task: &actions_model.ActionTask{ + Steps: []*actions_model.ActionTaskStep{ + {Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, + {Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090}, + }, + Status: actions_model.StatusSuccess, + Started: 10000, + Stopped: 10100, + LogLength: 100, + }, + want: []*actions_model.ActionTaskStep{ + {Name: preStepName, Status: actions_model.StatusSuccess, LogIndex: 0, LogLength: 10, Started: 10000, Stopped: 10010}, + {Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, + {Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090}, + {Name: postStepName, Status: actions_model.StatusSuccess, LogIndex: 90, LogLength: 10, Started: 10090, Stopped: 10100}, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 4ff86b5a66..9396115b0d 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -317,7 +317,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b case git.EntryModeBlob: ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path), tplEditFile, &form) default: - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", fileErr.Path), tplEditFile, &form) } } else { ctx.Error(http.StatusInternalServerError, err.Error()) diff --git a/web_src/js/features/repo-editor.ts b/web_src/js/features/repo-editor.ts index dadbd802bc..e4d179d3ae 100644 --- a/web_src/js/features/repo-editor.ts +++ b/web_src/js/features/repo-editor.ts @@ -75,24 +75,63 @@ export function initRepoEditor() { } filenameInput.addEventListener('input', function () { const parts = filenameInput.value.split('/'); + const links = Array.from(document.querySelectorAll('.breadcrumb span.section')); + const dividers = Array.from(document.querySelectorAll('.breadcrumb .breadcrumb-divider')); + let warningDiv = document.querySelector('.ui.warning.message.flash-message.flash-warning.space-related'); + let containSpace = false; if (parts.length > 1) { for (let i = 0; i < parts.length; ++i) { const value = parts[i]; + const trimValue = value.trim(); + if (trimValue === '..') { + // remove previous tree path + if (links.length > 0) { + const link = links.pop(); + const divider = dividers.pop(); + link.remove(); + divider.remove(); + } + continue; + } if (i < parts.length - 1) { - if (value.length) { - filenameInput.before(createElementFromHTML( + if (trimValue.length) { + const linkElement = createElementFromHTML( `${htmlEscape(value)}`, - )); - filenameInput.before(createElementFromHTML( + ); + const dividerElement = createElementFromHTML( ``, - )); + ); + links.push(linkElement); + dividers.push(dividerElement); + filenameInput.before(linkElement); + filenameInput.before(dividerElement); } } else { filenameInput.value = value; } this.setSelectionRange(0, 0); + containSpace |= (trimValue !== value && trimValue !== ''); } } + containSpace |= Array.from(links).some((link) => { + const value = link.querySelector('a').textContent; + return value.trim() !== value; + }); + containSpace |= parts[parts.length - 1].trim() !== parts[parts.length - 1]; + if (containSpace) { + if (!warningDiv) { + warningDiv = document.createElement('div'); + warningDiv.classList.add('ui', 'warning', 'message', 'flash-message', 'flash-warning', 'space-related'); + warningDiv.innerHTML = '

File path contains leading or trailing whitespace.

'; + // Add display 'block' because display is set to 'none' in formantic\build\semantic.css + warningDiv.style.display = 'block'; + const inputContainer = document.querySelector('.repo-editor-header'); + inputContainer.insertAdjacentElement('beforebegin', warningDiv); + } + showElem(warningDiv); + } else if (warningDiv) { + hideElem(warningDiv); + } joinTreePath(); }); filenameInput.addEventListener('keydown', function (e) {