Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Add Repositories UI in the File Explorer Dev Home Page #3488

Conversation

ssparach
Copy link
Contributor

@ssparach ssparach commented Jul 23, 2024

Summary of the pull request

This PR removes the temporary UI and implements the design based UI for adding and removing repositories (for source control integration) in the File Explorer Settings page.

References and relevant issues

Detailed description of the pull request / Additional comments

image

Validation steps performed

Built msix release pckg
Validated UI functionality

PR checklist

  • Closes #xxx
  • Tests added/passed
  • Documentation updated

@ssparach ssparach marked this pull request as ready for review July 23, 2024 23:16
@ssparach ssparach removed the request for review from ethanfangg July 23, 2024 23:16
<DropDownButton.Flyout>
<MenuFlyout
Placement="Bottom">
<!-- WinUI does not currently allow populating MenuFlyoutItems dynamically within a DataTemplate. -->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please file an issue/bug to track this. We only have the one provider at the moment, so this shouldn't block some testing/selfhosting. But let's not forget to come back to this and fix it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this as a potential fix. Using an MSBuild task to iterate through all the extensions and populate XAML before it is built. It would be difficult though. Just an idea.

@ssparach ssparach force-pushed the feature/fileexplorer-sourcecontrol-integration branch 3 times, most recently from 16b9823 to e7bc4e1 Compare July 31, 2024 23:27
ssparach and others added 13 commits August 1, 2024 11:39
**Summary of the pull request**
This PR contains implementation changes for the File Explorer Source Control Integration experimental feature. This feature will allow File Explorer to obtain property information from source control technologies for display (image attached below):

**Detailed description of the pull request / Additional comments**
This PR contains the following changes:
- Reference official Dev Home SDK version (that defines APIs for File Explorer Source Control Integration)
- Declare FileExplorerSourceControlIntegration as an experimental feature
- The FileExplorerSourceControlIntegration project which creates a COM Server used to communicate information with File Explorer
- The FileExplorerGitIntegration project which allows Dev Home to come with an inbox extension that understands git
- Basic Unit Tests

**Validation steps performed**
SDK and MSIX local builds
Tested Git Integration File Explorer behavior inside VM

Related work items: #48431506
* declare appextension for git, find all source control extensions, UI changes, SDK changes, get extension information to use for mapping

* changes to map extension to registered root paths, add validation to git implementation

* changes after testing

* use serilog in validation code

* reorder using

* use published SDK version

* address PR feedback

* address PR feedback

* address PR feedback

* Minor cleanup of RepositoryTracking.cs

---------

Co-authored-by: Ryan Shepherd <ryansh@microsoft.com>
latestCommit = FindLatestCommit(relativePath, repository);
if (latestCommit != null)
{
result.Add("System.VersionControl.LastChangeMessage", latestCommit.MessageShort);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PropName can be used here instead of re-typing the string.

return result;
}

foreach (var propName in properties)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the null checks are used because of iterating through the properties and this needs the latest commit to be not null.

I don't like the duplication of the check.

If LatestCommit returns null for a property, would a second call to LatestCommit return a non-null value? If this is the case the call to LatestCommit can be moved outside the foreach loop.

The null check for LatestCommit still needs to be in each case "If not null, add the property"

{
switch (propName)
{
case "System.VersionControl.LastChangeMessage":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way to do this is,

  1. Put LatestCommit outside of the for loop.
  2. For every property use result.Add(propName, latestCommit?.[PropertyName] ?? string.empty)
  3. Before returning result remove all entries that contain string.empty.

var commits = repository.Commits;

// Check the most recent commit and bail if the file is not present
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this in its own scope?

// Check the most recent commit and bail if the file is not present
{
var firstCommit = commits.FirstOrDefault();
if (firstCommit != null && firstCommit.Tree[relativePath] == null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this can be simplified to commits.FirstOrDefault()?.Tree?[relativePath] == null

}
}

foreach (var currentCommit in commits)
Copy link
Contributor

@dhoehna dhoehna Aug 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: commit.Select(x => x.Tree[relativePath]).Where(x +> x is not null)


var parents = currentCommit.Parents;
var count = parents.Count();
if (count == 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: currentCommit.Parents.Count() == 0

catch (Exception ex)
{
log.Error("RepositoryCache", "Failed to create Repository", ex);
if (repo != null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: repo?.Dispose()

public static void Main([System.Runtime.InteropServices.WindowsRuntime.ReadOnlyArray] string[] args)
{
// Set up Logging
Environment.SetEnvironmentVariable("DEVHOME_LOGS_ROOT", Path.Join(DevHome.Common.Logging.LogFolderRoot, "FileExplorerGitIntegration"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: You can use Microsoft.Windows.System.EnvironmentManager and the EV will be removed when the app is uninstalled.

return new SourceControlValidationResult(ResultType.Failure, ErrorType.RepositoryProvderCreationFailed, null, null, null);
}

ILocalRepositoryProvider provider = MarshalInterface<ILocalRepositoryProvider>.FromAbi(providerPtr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use PInvoke to get the ILocalRepositoryProvider? I believe DevHome has interfaces to get the providers already.

{
lock (trackRepoLock)
{
var caseSensitiveDictionary = fileService.Read<Dictionary<string, string>>(RepoStoreOptions.RepoStoreFolderPath, RepoStoreOptions.RepoStoreFileName);
Copy link
Contributor

@dhoehna dhoehna Aug 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

old name? Can the name be changed to something more descriptive?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering on line 72 the case is ignored.

{
if (!TrackedRepositories.ContainsKey(rootPath))
{
TrackedRepositories[rootPath] = extensionCLSID!;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand. Tracked repositories is a map from a root path to the extension that is tracking the repository. I understand now. Okay.

NIT: change the name to TrackedRepositoriesToExtension, or something similar.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is why so many other files use pUnk to determing the extension type.

if (TrackedRepositories.TryGetValue(rootPath, out var value))
{
log.Information("Source Control Provider returned for root path");
return TrackedRepositories[rootPath];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return value.

@@ -0,0 +1,180 @@
// Copyright (c) Microsoft Corporation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT for the file: Make CLSID the second argument. That is what it is. All other methods need either

  1. nothing
  2. RootPath
  3. RootPath and CLSID.

This keeps the arguments in a consistent order.

@ssparach
Copy link
Contributor Author

ssparach commented Aug 1, 2024

@dhoehna Just a heads up. This PR contains alot of changes which have already been merged to the feature branch. Since the feature branch was rebased to latest in main, the changes are appearing here. I am currently (locally) fixing the state of the branch. As such, the PR will only contain the UI changes. However, we do plan to create a PR for review to commit to main soon. Thanks!

{
if (TrackedRepositories.TryGetValue(rootPath, out var existingExtensionCLSID))
{
TrackedRepositories[rootPath] = extensionCLSID;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you already retrieved TrackedRepositories[rootPath] in TryGetValue. existingExtensionCLSID can be used in this scope.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NVM. This is used to check the existence of the key. In the case. can you use .ContainsKey instead?

TrackedRepositories = caseSensitiveDictionary.ToDictionary(entry => entry.Key, entry => entry.Value, StringComparer.OrdinalIgnoreCase);
}

LastRestore = DateTime.Now;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: use UTcNow. An edge case can occur if using .Now. For example, Daylight savings time occurred. Or the user moved between times zones.


internal ILocalRepositoryProvider GetLocalProvider(string rootPath)
{
// TODO: Iterate extensions to find the correct one for this rootPath.
Copy link
Contributor

@dhoehna dhoehna Aug 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this TODO still relevant?

{
TrackedRepositories.Clear();
var repoCollection = RepoTracker.GetAllTrackedRepositories();
foreach (KeyValuePair<string, string> data in repoCollection)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: repoCollection.Select(x => new RepositoryInformation(x.Key, x.Value))


IDictionary<string, object> IExtraFolderPropertiesHandler.GetProperties(string[] propertyStrings, string rootFolderPath, string relativePath)
{
var localProvider = GetLocalProvider(rootFolderPath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: GetLocalProvider(rootFolderPath).GetRepository(rootFolderPath)

}

// TODO: How to detect between WinRT/IInspectable and COM/IUnknown interfaces
ppvObject = MarshalInspectable<T>.CreateMarshaler2(_createSourceControlProvider(), riid, true).Detach();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this TODO still relevant?

@ssparach ssparach force-pushed the user/ssparach/AddRepositoriesUIForFEVersionControlSettings branch from 2ea72d7 to 3ae6b69 Compare August 1, 2024 22:56
@ssparach ssparach merged commit 136ea6e into feature/fileexplorer-sourcecontrol-integration Aug 2, 2024
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants