PR Creation: Push local branches and handle forks properly #502

Merged
merged 15 commits into from Aug 19, 2016

1 participant

@shana
shana commented Aug 11, 2016 edited

Fixes #501

When creating PRs, we should do the following

  • Push local commits
  • Set local branch to track remote branch if it's not
  • If local branch is tracking a differently-named remote branch, use that remote branch name as the source for the PR
  • If the repo is a fork, list all branches from both local and upstream and set upstream master as the default PR target branch
@shana

Ok, this is ready for a first pass at review. There's no progress bars yet while creating a PR, but the form is disabled while it's happening. If a PR is created from a fork to a branch upstream, the list won't show any PRs, which will certainly be confusing, so we need to show a messsage indicating the link to the newly created PR, which is not done yet.

Expected behaviour:

  • The list of branches should show upstream branches (prefixed with owner of repo) and list of fork branches, if the repo is a fork. If it isn't, it should only show the repo branches, no prefix
  • When creating a PR from a fork, it should select the upstream default branch.
  • When creating a PR from a non-fork, it should select the repo's default branch
  • When creating a PR targeting the same repo, the PR should show up on the list afterwards
  • When creating a PR targeting an upstream branch, a message should show up linking to the new PR on the website (it's not listed) - this isn't happening yet
  • While loading data, form should be disabled (might be too fast to see)
  • While processing a PR creation, form should be disabled
@shana shana Implement creating PRs in forks and pushing branches
220ce43
@shana shana commented on the diff Aug 16, 2016
src/GitHub.App/Services/RepositoryPublishService.cs
@@ -37,13 +37,12 @@ public string LocalRepositoryName
IAccount account,
IApiClient apiClient)
{
- return Observable.Defer(() => Observable.Return(activeRepository))
- .SelectMany(r => apiClient.CreateRepository(newRepository, account.Login, account.IsUser)
- .Select(gitHubRepo => Tuple.Create(gitHubRepo, r)))
- .SelectMany(repo => gitClient.SetRemote(repo.Item2, "origin", new Uri(repo.Item1.CloneUrl)).Select(_ => repo))
- .SelectMany(repo => gitClient.Push(repo.Item2, "master", "origin").Select(_ => repo))
- .SelectMany(repo => gitClient.Fetch(repo.Item2, "origin").Select(_ => repo))
- .SelectMany(repo => gitClient.SetTrackingBranch(repo.Item2, "master", "origin").Select(_ => repo.Item1));
+ return Observable.Defer(() => apiClient.CreateRepository(newRepository, account.Login, account.IsUser)
+ .Select(remoteRepo => new { RemoteRepo = remoteRepo, LocalRepo = activeRepository }))
@shana
shana added a note Aug 16, 2016

Ooops, I forgot I included this. I've been meaning to clean up this tuple for ages and always end up not doing it. Oh well, now done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@grokys grokys commented on an outdated diff Aug 17, 2016
src/GitHub.App/Services/PullRequestService.cs
}
- }
+ return Observable.Return(ret);
@grokys
grokys added a note Aug 17, 2016

This will return an observable that contains null if a template is not found, but it should probably return an empty sequence. It was returning null before to say "template file not found" but now we're using observables an empty sequence is a more natural fit for that,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@grokys grokys and 1 other commented on an outdated diff Aug 17, 2016
src/GitHub.App/Services/PullRequestService.cs
- return null;
+ if (!repo.Branches[sourceBranch.Name].IsTracking)
+ await gitClient.SetTrackingBranch(repo, sourceBranch.Name, remote.Name);
+
+ // delay things a bit to avoid a race between pushing a new branch and creating a PR on it
+ if (Splat.ModeDetector.Current.InUnitTestRunner().GetValueOrDefault())
@grokys
grokys added a note Aug 17, 2016 edited

Shouldn't this be delaying if we're not in the unit test runner?

@shana
shana added a note Aug 17, 2016

Bleh, yes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@grokys grokys commented on the diff Aug 17, 2016
src/GitHub.App/Services/RepositoryPublishService.cs
@@ -37,13 +37,12 @@ public string LocalRepositoryName
IAccount account,
IApiClient apiClient)
{
- return Observable.Defer(() => Observable.Return(activeRepository))
- .SelectMany(r => apiClient.CreateRepository(newRepository, account.Login, account.IsUser)
- .Select(gitHubRepo => Tuple.Create(gitHubRepo, r)))
- .SelectMany(repo => gitClient.SetRemote(repo.Item2, "origin", new Uri(repo.Item1.CloneUrl)).Select(_ => repo))
- .SelectMany(repo => gitClient.Push(repo.Item2, "master", "origin").Select(_ => repo))
- .SelectMany(repo => gitClient.Fetch(repo.Item2, "origin").Select(_ => repo))
- .SelectMany(repo => gitClient.SetTrackingBranch(repo.Item2, "master", "origin").Select(_ => repo.Item1));
+ return Observable.Defer(() => apiClient.CreateRepository(newRepository, account.Login, account.IsUser)
+ .Select(remoteRepo => new { RemoteRepo = remoteRepo, LocalRepo = activeRepository }))
+ .SelectMany(repo => gitClient.SetRemote(repo.LocalRepo, "origin", new Uri(repo.RemoteRepo.CloneUrl)).Select(_ => repo))
+ .SelectMany(repo => gitClient.Push(repo.LocalRepo, "master", "origin").Select(_ => repo))
+ .SelectMany(repo => gitClient.Fetch(repo.LocalRepo, "origin").Select(_ => repo))
+ .SelectMany(repo => gitClient.SetTrackingBranch(repo.LocalRepo, "master", "origin").Select(_ => repo.RemoteRepo));
@grokys
grokys added a note Aug 17, 2016

Would the intention here would be clear as:

            return Observable.Defer(() => apiClient.CreateRepository(newRepository, account.Login, account.IsUser)
                         .Select(remoteRepo => new { RemoteRepo = remoteRepo, LocalRepo = activeRepository }))
                    .Do(repo => gitClient.SetRemote(repo.LocalRepo, "origin", new Uri(repo.RemoteRepo.CloneUrl)))
                    .Do(repo => gitClient.Push(repo.LocalRepo, "master", "origin"))
                    .Do(repo => gitClient.Fetch(repo.LocalRepo, "origin"))
                    .Do(repo => gitClient.SetTrackingBranch(repo.LocalRepo, "master", "origin"))
                    .Select(x => x.RemoteRepo);

?

The SelectMany statements aren't really "selecting" - they're used for side-effects, so Do seems more relevant.

@grokys
grokys added a note Aug 17, 2016

Actually no, ingore this: Do won't wait for the previous operation to finish.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@grokys grokys commented on an outdated diff Aug 17, 2016
...GitHub.App/ViewModels/PullRequestCreationViewModel.cs
TitleValidator = ReactivePropertyValidator.ForObservable(titleObs)
.IfNullOrEmpty(Resources.PullRequestCreationTitleValidatorEmpty);
var branchObs = this.WhenAny(
- x => x.TargetBranch,
- x => x.SourceBranch,
- (target, source) => new { Source = source.Value, Target = target.Value })
- .Where(_ => initialized)
- .Merge(initializationComplete.Select(_ => new { Source = SourceBranch, Target = TargetBranch }));
+ x => x.TargetBranch,
+ x => x.SourceBranch,
+ (target, source) => new { Source = source.Value, Target = target.Value })
+ .Where(_ => Initialized)
+ .Merge(this.WhenAnyValue(x => x.Initialized).Where(x => x).Select(_ => new { Source = SourceBranch, Target = TargetBranch }));
@grokys
grokys added a note Aug 17, 2016

Good change to remove the initializationComplete! However I think this could be further simplified:

            var branchObs = this.WhenAnyValue(
                    x => x.Initialized,
                    x => x.TargetBranch,
                    x => x.SourceBranch,
                    (init, target, source) => new { Initialized = init, Source = source, Target = target })
                .Where(x => x.Initialized);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@grokys grokys and 1 other commented on an outdated diff Aug 17, 2016
...GitHub.App/ViewModels/PullRequestCreationViewModel.cs
@@ -96,68 +119,93 @@ public class PullRequestCreationViewModel : BaseViewModel, IPullRequestCreationV
notifications.ShowError(error?.Message ?? ex.Message);
return Observable.Empty<IPullRequestModel>();
}));
+ isExecuting = CreatePullRequest.IsExecuting.ToProperty(this, x => x.IsExecuting);
@grokys
grokys added a note Aug 17, 2016

Are IsExecuting and IsBusy not the same thing? Are they related?

@shana
shana added a note Aug 17, 2016

IsBusy reflects a combination of multiple states that signify the vm is doing something and so data should not be externally changed until that something is finished (when loading branches, initializing, executing the creation, etc)

I really don't like having to have this IsExecuting property just to listen to the changes in the observable of the button, but right now it's the best way to combine it with the rest of the property changes. I really want to find a cleaner way of doing this next week.

@grokys
grokys added a note Aug 17, 2016

👍 i guess this will become clear when the IsBusy state is actually updated.

@grokys
grokys added a note Aug 17, 2016

Oh, and then looking 3 lines below I see it is. I missed that previously, it would've answered my question.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@grokys grokys and 1 other commented on an outdated diff Aug 17, 2016
...GitHub.App/ViewModels/PullRequestCreationViewModel.cs
}
+ public IReadOnlyList<IBranch> Branches => branchesList;
@grokys
grokys added a note Aug 17, 2016

Why are both Branches and BranchesList needed? The branches are never mutated after they're set so can't there be just Branches?

        IReadOnlyList<IBranch> branches;
        public IReadOnlyList<IBranch> Branches
        {
            get { return branches; }
            private set
            {
                branches = value;
                this.RaisePropertyChanged();
            }
        }
@shana
shana added a note Aug 17, 2016

They were mutated at one point in this rewrite, but they're not anymore. I'll fixicate!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@grokys grokys commented on the diff Aug 17, 2016
...Exports/Extensions/SimpleRepositoryModelExtensions.cs
@@ -10,7 +10,7 @@ public static class SimpleRepositoryModelExtensions
{
public static bool HasCommits(this ISimpleRepositoryModel repository)
{
- var repo = GitService.GitServiceHelper.GetRepo(repository.LocalPath);
+ var repo = GitService.GitServiceHelper.GetRepository(repository.LocalPath);
@grokys
grokys added a note Aug 17, 2016

👍 to not using abbreviations! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@grokys grokys and 1 other commented on an outdated diff Aug 17, 2016
src/GitHub.Exports/Models/IBranch.cs
string Name { get; }
+ ISimpleRepositoryModel Repository { get; set; }
+ bool IsTracking { get; set; }
+ string DisplayName { get; set; }
@grokys
grokys added a note Aug 17, 2016

Could some of these properties be made readonly? It kinda worries me to have them all read/write as it can be difficult to tell how they can change. In what circumstances can they change?

@shana
shana added a note Aug 17, 2016

Remnants of prototyping. DisplayName is the only one that's currently modified externally based on what the viewmodel wants the branch to look like in lists.

@grokys
grokys added a note Aug 17, 2016

Ok - yeah it makes sense for DisplayName to be mutable to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@grokys grokys and 1 other commented on an outdated diff Aug 17, 2016
src/GitHub.Exports/Models/SimpleRepositoryModel.cs
}
bool IEquatable<SimpleRepositoryModel>.Equals(SimpleRepositoryModel other)
{
if (ReferenceEquals(this, other))
return true;
- return other != null && String.Equals(Name, other.Name) && String.Equals(CloneUrl, other.CloneUrl) && String.Equals(LocalPath?.TrimEnd('\\'), other.LocalPath?.TrimEnd('\\'), StringComparison.CurrentCultureIgnoreCase);
+ return other != null && String.Equals(Name, other.Name) && String.Equals(Owner, other.Owner) && String.Equals(CloneUrl, other.CloneUrl) && String.Equals(LocalPath?.TrimEnd('\\'), other.LocalPath?.TrimEnd('\\'), StringComparison.CurrentCultureIgnoreCase);
@grokys
grokys added a note Aug 17, 2016 edited

Kinda worries me to see this long line copy/pasted in 2 places. Could you make Equals(SimpleRepositoryModel) a normal method (not an explicit interface method) and call Equals(SimpleRepositoryModel) from Equals(object)?

@shana
shana added a note Aug 17, 2016

Yes, definitely should not have this in two places. Good catch!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@grokys grokys commented on an outdated diff Aug 17, 2016
src/GitHub.Exports/Services/GitService.cs
/// <returns></returns>
- public static UriString GetOriginUri(IRepository repo)
+ public UriString GetOriginUri(IRepository repo, string remote = "origin")
@grokys
grokys added a note Aug 17, 2016

This probably should now be called "GetRemoteUri" and shouldn't have "origin" in its doc comments any more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
shana added some commits Aug 17, 2016
@shana shana Fix PR template loading return value to be sane
e82d69f
@shana shana We don't want to delay on unit tests...
3861ba3
@shana shana Dedupe code
3dc820f
@shana shana Simplify things 72c3c4f
@shana shana Get rid of useless duplicated list
ed8bc64
@shana shana Fix setter accessibility
a66d8f2
@shana shana Fix method name to be more explicit
a28191b
@shana shana Show a message when PR is created upstream
851074b
@shana

Ok, we're now showing notifications when a PR is created, with a link to the created PR on the website. If the PR is created upstream, it won't show up on the PR list but the user will be able to go to it so hopefully it won't be too confusing.

This is how it works (the url opened is wrong in this gif but that's already fixed)
creating-a-pr-upstream

shana added some commits Aug 19, 2016
@shana shana Add comment on code assumptions
e2e7fb7
@shana shana Add link to source of markdown renderer
2eb5782
@shana shana commented on the diff Aug 19, 2016
src/GitHub.Exports/ViewModels/IInfoPanel.cs
@@ -0,0 +1,14 @@
+namespace GitHub.ViewModels
+{
+ public interface IInfoPanel
+ {
+ string Message { get; set; }
+ MessageType MessageType { get; set; }
+ }
@shana
shana added a note Aug 19, 2016

Interface probably not needed, but added it anyway to have a place to keep the MessageType enum and be explicit about things (maybe unit test it later, too)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
shana added some commits Aug 19, 2016
@shana shana Dispose form when leaving it, try not to leak
c346846
@shana shana Merge branch 'master' into fixes/501-push-new-branch-on-pr-creation
6fe8446
@shana shana Always go to the server when loading/refreshing PRs
Trust the existing cached results for 7 days, we'll always refresh so
should be fine.
009d24e
@shana
shana commented Aug 19, 2016 edited

This also fixes #407 and #481

@shana shana Disable cache test, not valid anymore
f1ad348
@shana shana merged commit ed8d0e8 into master Aug 19, 2016

5 checks passed

Details GitHub CLA @shana has accepted the GitHub Contributor License Agreement.
Details VisualStudio Build #3894835 succeeded in 83s
Details continuous-integration/appveyor/branch AppVeyor build succeeded
Details continuous-integration/appveyor/pr AppVeyor build succeeded
Details jenkins/build_log Jenkins Build Log
@shana shana deleted the fixes/501-push-new-branch-on-pr-creation branch Aug 19, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment