diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..22d5376407 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +# especially +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot#enabling-dependabot-version-updates-for-actions + +version: 2 +updates: + - package-ecosystem: "github-actions" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a30be08797..97859a15f5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -42,25 +42,25 @@ jobs: run: src\scripts\CreateBuildArtifacts.bat ${{ matrix.configuration }} artifacts - name: Upload functional tests drop - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: FunctionalTests_${{ matrix.configuration }} path: artifacts\GVFS.FunctionalTests - name: Upload FastFetch drop - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: FastFetch_${{ matrix.configuration }} path: artifacts\FastFetch - name: Upload installers - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Installers_${{ matrix.configuration }} path: artifacts\GVFS.Installers - name: Upload NuGet packages - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: NuGetPackages_${{ matrix.configuration }} path: artifacts\NuGetPackages @@ -76,13 +76,13 @@ jobs: steps: - name: Download installers - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: Installers_${{ matrix.configuration }} path: install - name: Download functional tests drop - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: FunctionalTests_${{ matrix.configuration }} path: ft @@ -101,7 +101,7 @@ jobs: - name: Upload installation logs if: always() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: InstallationLogs_${{ matrix.configuration }} path: install\logs @@ -115,14 +115,14 @@ jobs: - name: Upload functional test results if: always() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: FunctionalTests_Results_${{ matrix.configuration }} path: TestResult.xml - name: Upload Git trace2 output if: always() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: GitTrace2_${{ matrix.configuration }} path: C:\temp\git-trace2.log diff --git a/GVFS/GVFS.Common/Http/HttpRequestor.cs b/GVFS/GVFS.Common/Http/HttpRequestor.cs index 1f5271d679..ca850c9fb5 100644 --- a/GVFS/GVFS.Common/Http/HttpRequestor.cs +++ b/GVFS/GVFS.Common/Http/HttpRequestor.cs @@ -134,6 +134,14 @@ protected GitEndPointResponseData SendRequest( { response = this.client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).GetAwaiter().GetResult(); } + catch (HttpRequestException httpRequestException) when (TryGetResponseMessageFromHttpRequestException(httpRequestException, request, out response)) + { + /* HttpClientHandler will automatically resubmit in certain circumstances, such as a 401 unauthorized response when UseDefaultCredentials + * is true but another credential was provided. This resubmit can throw (instead of returning a proper status code) in some case cases, such + * as when there is an exception loading the default credentials. + * If we can extract the original response message from the exception, we can continue and process the original failed status code. */ + Tracer.RelatedWarning(responseMetadata, $"An exception occurred while resubmitting the request, but the original response is available."); + } finally { responseWaitTime = requestStopwatch.Elapsed; @@ -278,5 +286,48 @@ private static string GetSingleHeaderOrEmpty(HttpHeaders headers, string headerN return string.Empty; } + + /// + /// This method is based on a private method System.Net.Http.HttpClientHandler.CreateResponseMessage + /// + private static bool TryGetResponseMessageFromHttpRequestException(HttpRequestException httpRequestException, HttpRequestMessage request, out HttpResponseMessage httpResponseMessage) + { + var webResponse = (httpRequestException?.InnerException as WebException)?.Response as HttpWebResponse; + if (webResponse == null) + { + httpResponseMessage = null; + return false; + } + + httpResponseMessage = new HttpResponseMessage(webResponse.StatusCode); + httpResponseMessage.ReasonPhrase = webResponse.StatusDescription; + httpResponseMessage.Version = webResponse.ProtocolVersion; + httpResponseMessage.RequestMessage = request; + httpResponseMessage.Content = new StreamContent(webResponse.GetResponseStream()); + request.RequestUri = webResponse.ResponseUri; + WebHeaderCollection rawHeaders = webResponse.Headers; + HttpContentHeaders responseContentHeaders = httpResponseMessage.Content.Headers; + HttpResponseHeaders responseHeaders = httpResponseMessage.Headers; + if (webResponse.ContentLength >= 0) + { + responseContentHeaders.ContentLength = webResponse.ContentLength; + } + + for (int i = 0; i < rawHeaders.Count; i++) + { + string key = rawHeaders.GetKey(i); + if (string.Compare(key, "Content-Length", StringComparison.OrdinalIgnoreCase) != 0) + { + string[] values = rawHeaders.GetValues(i); + if (!responseHeaders.TryAddWithoutValidation(key, values)) + { + bool flag = responseContentHeaders.TryAddWithoutValidation(key, values); + } + } + } + + return true; + + } } }