[git] IJPL-84816 Suggest to unshallow repository

GitOrigin-RevId: 197ff50c9347d58aaea6400666236305511bf5c6
This commit is contained in:
Ilia.Shulgin
2024-10-07 13:44:36 +00:00
committed by intellij-monorepo-bot
parent 08e08e8603
commit 68707f6fc9
7 changed files with 175 additions and 12 deletions
@@ -514,6 +514,8 @@
<add-to-group group-id="CopyFileReference"/>
<add-to-group group-id="Copy.Paste.Special" relative-to-action="CopyReference" anchor="after"/>
</group>
<action id="Git.Unshallow" class="git4idea.actions.GitUnshallowRepositoryAction"/>
</actions>
<extensions defaultExtensionNs="com.intellij">
@@ -728,6 +730,7 @@
<backgroundPostStartupActivity implementation="git4idea.ignore.GitIgnoreInStoreDirGeneratorActivity"/>
<backgroundPostStartupActivity implementation="git4idea.config.GitSetupProjectConfig"/>
<backgroundPostStartupActivity implementation="git4idea.repo.GitShallowRepositoryCheck"/>
<registryKey key="git.update.incoming.info.time" defaultValue="20"
description="Time period (in minutes) between incoming commits checks." restartRequired="true"/>
@@ -484,6 +484,9 @@ unstash.clear.confirmation.title=Remove All Stashes?
unstash.drop.confirmation.message=<html>Do you want to remove {0}?<br/>"{1}"</html>
unstash.drop.confirmation.title=Remove Stash {0}?
unshallow.repository.notification.message=Shallow repository detected
unshallow.repository.notification.title=This repository can be converted to a complete one
notification.title.unstash.failed=Unstash failed
notification.title.unstash.failed.index.conflict=Unstash failed due to an index conflict
exception.message.could.not.stash.root.error=Cannot stash {0}: {1}
@@ -689,6 +692,9 @@ action.main.toolbar.git.Branches.text=VCS Widget
action.main.toolbar.git.MergeRebase.text=VCS Merge/Rebase Widget
action.not.possible.in.fresh.repo.generic=Action
action.main.toolbar.git.project.not.trusted.separator.text=Safe mode, Version Control is not available
action.Git.Unshallow.text=Unshallow Repository
action.Git.Unshallow.progress.title=Unshallowing repository\u2026
action.Git.Unshallow.failure.title=Failed to unshallow repository
settings.git.option.group=Git
settings.commit.group.title=Commit
@@ -0,0 +1,47 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package git4idea.actions
import com.intellij.dvcs.DvcsUtil
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.DumbAwareAction
import git4idea.GitVcs
import git4idea.fetch.GitFetchSupport
import git4idea.i18n.GitBundle
import git4idea.repo.GitRepository
import git4idea.repo.GitRepositoryManager
internal class GitUnshallowRepositoryAction : DumbAwareAction() {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
override fun update(e: AnActionEvent) {
val project = e.project
val guessedRepo: GitRepository? = project?.let {
DvcsUtil.guessRepositoryForOperation(it, GitRepositoryManager.getInstance(it), e.dataContext)
}
e.presentation.isEnabledAndVisible = guessedRepo != null && guessedRepo.info.isShallow
}
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val repository =
DvcsUtil.guessRepositoryForOperation(project, GitRepositoryManager.getInstance(project), e.dataContext)
?: return
val fetcher = GitFetchSupport.fetchSupport(project)
val remote = fetcher.getDefaultRemoteToFetch(repository) ?: return
GitVcs.runInBackground(object : Task.Backgroundable(project, GitBundle.message("action.Git.Unshallow.progress.title")) {
override fun run(indicator: ProgressIndicator) {
fetcher.fetchUnshallow(repository, remote).showNotificationIfFailed(GitBundle.message("action.Git.Unshallow.failure.title"))
}
})
}
internal companion object {
const val ACTION_ID = "Git.Unshallow"
}
}
@@ -35,6 +35,9 @@ public interface GitFetchSupport {
@NotNull
GitFetchResult fetch(@NotNull GitRepository repository, @NotNull GitRemote remote);
@NotNull
GitFetchResult fetchUnshallow(@NotNull GitRepository repository, @NotNull GitRemote remote);
/**
* Fetches the given remotes.
*/
@@ -97,6 +97,10 @@ internal class GitFetchSupportImpl(private val project: Project) : GitFetchSuppo
return fetch(listOf(RemoteRefCoordinates(repository, remote)))
}
override fun fetchUnshallow(repository: GitRepository, remote: GitRemote): GitFetchResult {
return fetch(listOf(RemoteRefCoordinates(repository, remote, unshallow = true)))
}
override fun fetch(repository: GitRepository, remote: GitRemote, refspec: @NonNls String): GitFetchResult {
return fetch(listOf(RemoteRefCoordinates(repository, remote, refspec)))
}
@@ -152,7 +156,10 @@ internal class GitFetchSupportImpl(private val project: Project) : GitFetchSuppo
val executor = AppExecutorUtil.createBoundedApplicationPoolExecutor("GitFetch pool", maxThreads)
val commonIndicator = progressManager.progressIndicator ?: EmptyProgressIndicator()
val authenticationGate = RestrictingAuthenticationGate()
for ((repository, remote, refspec) in remotes) {
for (fetchTarget in remotes) {
val repository = fetchTarget.repository
val remote = fetchTarget.remote
LOG.debug("Fetching $remote in $repository")
val future: Future<SingleRemoteResult> = executor.submit<SingleRemoteResult> {
commonIndicator.checkCanceled()
@@ -161,7 +168,7 @@ internal class GitFetchSupportImpl(private val project: Project) : GitFetchSuppo
ProgressManager.getInstance().executeProcessUnderProgress({
commonIndicator.checkCanceled()
result = fetchQueue.executeForRemote(repository, remote) {
doFetch(repository, remote, refspec, authenticationGate)
doFetch(fetchTarget, authenticationGate)
}
}, commonIndicator)
result
@@ -229,9 +236,7 @@ internal class GitFetchSupportImpl(private val project: Project) : GitFetchSuppo
}
}
private fun doFetch(repository: GitRepository, remote: GitRemote, refspec: String?, authenticationGate: AuthenticationGate? = null)
: SingleRemoteResult {
private fun doFetch(fetchTarget: RemoteRefCoordinates, authenticationGate: AuthenticationGate?): SingleRemoteResult {
val indicator = progressManager.progressIndicator
val progressListener = GitLineHandlerListener { line, outputType ->
if (indicator != null && outputType == ProcessOutputTypes.STDERR) {
@@ -241,8 +246,15 @@ internal class GitFetchSupportImpl(private val project: Project) : GitFetchSuppo
}
val recurseSubmodules = "--recurse-submodules=no"
val params = if (refspec == null) arrayOf(recurseSubmodules) else arrayOf(refspec, recurseSubmodules)
val result = git.fetch(repository, remote, listOf(progressListener), authenticationGate, *params)
val params = buildList {
if (fetchTarget.refspec != null) add(fetchTarget.refspec)
add(recurseSubmodules)
if (fetchTarget.unshallow) add("--unshallow")
}.toTypedArray()
val repository = fetchTarget.repository
val remote = fetchTarget.remote
val result = git.fetch(fetchTarget.repository, remote, listOf(progressListener), authenticationGate, *params)
val pruned = result.output.mapNotNull { getPrunedRef(it) }
if (result.success()) {
BackgroundTaskUtil.syncPublisher(repository.project, GIT_AUTHENTICATION_SUCCESS).authenticationSucceeded(repository, remote)
@@ -257,7 +269,12 @@ internal class GitFetchSupportImpl(private val project: Project) : GitFetchSuppo
return if (matcher.matches()) matcher.group(1) else null
}
private data class RemoteRefCoordinates(val repository: GitRepository, val remote: GitRemote, val refspec: String? = null)
private data class RemoteRefCoordinates(
val repository: GitRepository,
val remote: GitRemote,
val refspec: String? = null,
val unshallow: Boolean = false,
)
private class FetchTask(val repository: GitRepository, val remote: GitRemote, val future: Future<SingleRemoteResult>)
@@ -0,0 +1,50 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package git4idea.repo
import com.intellij.ide.util.RunOnceUtil
import com.intellij.notification.NotificationAction
import com.intellij.notification.NotificationType
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.vcs.VcsNotifier
import git4idea.actions.GitUnshallowRepositoryAction
import git4idea.fetch.GitFetchSupport
import git4idea.i18n.GitBundle
internal class GitShallowRepositoryCheck() : ProjectActivity {
override suspend fun execute(project: Project) {
val repository = GitRepositoryManager.getInstance(project).repositories.singleOrNull() ?: return
RunOnceUtil.runOnceForProject(project, ID) {
suggestToUnshallow(repository, project)
}
}
private fun suggestToUnshallow(repository: GitRepository, project: Project) {
if (repository.info.isShallow) {
val fetcher = GitFetchSupport.fetchSupport(project)
val remote = fetcher.getDefaultRemoteToFetch(repository)
if (remote == null) {
LOG.debug("Couldn't detect remote for shallow repository")
return
}
VcsNotifier.getInstance(project).notify(
VcsNotifier.importantNotification().createNotification(
GitBundle.message("unshallow.repository.notification.message"),
GitBundle.message("unshallow.repository.notification.title"),
NotificationType.INFORMATION,
).addAction(
NotificationAction.createExpiring(GitBundle.message("action.Git.Unshallow.text")) { e, _ ->
GitUnshallowRepositoryAction().actionPerformed(e)
}
)
)
}
}
}
private val LOG = Logger.getInstance(GitShallowRepositoryCheck::class.java)
private const val ID = "git.unshallow"
@@ -1,8 +1,11 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package git4idea.repo
import com.intellij.openapi.vcs.VcsException
import git4idea.GitVcs
import git4idea.commands.Git
import git4idea.commands.GitShallowCloneOptions
import git4idea.fetch.GitFetchSupport
import git4idea.test.GitSingleRepoTest
import git4idea.test.makeCommit
import git4idea.test.registerRepo
@@ -10,18 +13,52 @@ import kotlin.io.path.name
class GitShallowRepoTest: GitSingleRepoTest() {
fun `test shallow repo detection`() {
val copyRepo = setupShallowCLone()
assertFalse(repo.info.isShallow)
assertTrue(copyRepo.info.isShallow)
}
fun `test fetch unshallow repository`() {
val copyRepo = setupShallowCLone()
val fetchSupport = GitFetchSupport.fetchSupport(project)
val remote = fetchSupport.getDefaultRemoteToFetch(copyRepo)
assertNotNull(remote)
val fetchResult = fetchSupport.fetchUnshallow(copyRepo, remote!!)
fetchResult.throwExceptionIfFailed()
GitVcs.getInstance(project).vfsListener.waitForExternalFilesEventsProcessedInTestMode()
assertFalse(copyRepo.info.isShallow)
}
fun `test normal repository can't be unshallowed`() {
val cloneResult = createClone(null)
val fetchSupport = GitFetchSupport.fetchSupport(project)
val remote = fetchSupport.getDefaultRemoteToFetch(cloneResult)
assertNotNull(remote)
val fetchResult = fetchSupport.fetchUnshallow(cloneResult, remote!!)
assertThrows(VcsException::class.java) {
fetchResult.throwExceptionIfFailed()
}
}
private fun setupShallowCLone(): GitRepository {
makeCommit("1.txt")
makeCommit("2.txt")
makeCommit("3.txt")
assertFalse(repo.info.isShallow)
return createClone(GitShallowCloneOptions(1))
}
private fun createClone(shallowCloneOptions: GitShallowCloneOptions? = null): GitRepository {
val copy = projectNioRoot.resolve("copy")
val cloneResult = Git.getInstance().clone(project,
copy.parent.toFile(),
"file://${repo.root.path}", copy.name, GitShallowCloneOptions(1))
"file://${repo.root.path}", copy.name, shallowCloneOptions)
assertTrue(cloneResult.success())
val copyRepo = registerRepo(project, copy)
assertTrue(copyRepo.info.isShallow)
return registerRepo(this.project, copy)
}
}