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

Remove merged branches #159

Closed
wants to merge 17 commits into from
Closed

Conversation

tkirill
Copy link

@tkirill tkirill commented Oct 2, 2014

Add the cmdlets for removing merged branches:

  • Remove-MergedLocalGitBranches -- removes local merged branches.
  • Remove-MergedRemoteGitBranches -- removes remote merged branches.
  • Remove-MergedGitBranches -- removes both local and remote merged branches.

Implementation placed in the separate file GitRemoveMergedBranches.ps1 although most of other exported cmdlets sit in GitUtils.ps1. Hope it's not a code style rule to place everything inside GitUtils.ps1.

This is how these cmdlets work.

Remove-MergedLocalGitBranches

  1. Call git branch --merged.
  2. For each line trim whitespaces from both sides.
  3. Filter out line starting with "*" -- it is the current branch.
  4. Filter out ignored branches -- master and develop by default.
  5. If no branch remains then print message and return.
  6. For each remaining branch call git branch -d $item.

Remove-MergedRemoteGitBranches

Works same as Remove-MergedLocalGitBranches except three things:

  1. List of merged branches is retrieved by git branch -r --merged.
  2. Slightly more complex parsing and filtration. We need to filter out the line with "/HEAD ->" and cut off <remote-name>/ part from a beginning of each line.
  3. Removes branch with git push origin :$item.

Remove-MergedGitBranches
This one simply calls previous two cmdlets.

All three cmdlets support -WhatIf and -Confirm (link1, link2). Also, they all support -Ignore parameter for overriding list of ignored branches.

I haven't tested it enough yet to my shame but I think it is a good idea to create PR to receive feedback as soon as possible.

fixes #79

Verb 'Remove' was used instead of 'Delete' because it is approved verb: http://msdn.microsoft.com/en-us/library/ms714428%28v=vs.85%29.aspx.

There are three cmdlets:

*  Remove-MergedLocalGitBranches -- removes local merged branches.
*  Remove-MergedRemoteGitBranches -- removes remote merged branches.
*  Remove-MergedGitBranches -- removes both local and remote merged branches.

This is draft so there are no settings like target branch or remote repository name or list of branches to ignore.
@dahlbyk
Copy link
Owner

dahlbyk commented Nov 16, 2014

Terribly sorry for the delay in getting to a review of this (:new: :baby:) - this is a great start! Specific feedback incoming...

!$branch.StartsWith("*")
}

function TrimRemote-GitBranch($branch)
Copy link
Owner

Choose a reason for hiding this comment

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

Maybe switch this to a filter so you can replace ... | foreach {TrimRemote-GitBranch $_} | ... with ... | TrimRemote-GitBranch | ...?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, it looks better now (e093459).

@dahlbyk
Copy link
Owner

dahlbyk commented Nov 16, 2014

General question: would it be useful for any/all of these to accept a parameter (defaulting to HEAD) to identify the ref to check mergedness against, e.g. Remove-MergedGitBranches master?

@tkirill
Copy link
Author

tkirill commented Nov 19, 2014

General question: would it be useful for any/all of these to accept a parameter (defaulting to HEAD) to identify the ref to check mergedness against, e.g. Remove-MergedGitBranches master?

I have no strong opinion on this. As for me, it is unlikely I will ever need this parameter.

@tkirill
Copy link
Author

tkirill commented May 14, 2015

I found a bug. Remove-MergedBranches works bad with multiple remotes because it doesn't respect remote name in the output of git branch --remote --merged. For example:

C:\Users\t_kirill\workplace\gofra\repo [develop]> git branch --remote --merged
gitlab/counter-page-G-1202
gitlab/develop
gitlab/direct-link-excel
origin/HEAD -> origin/develop
origin/develop

If we have two remotes then merged branches will be deleted in the origin only. Remove-MergedBranches sees merged branches in remotes other than origin but can't delete them:

Deleting remote branch counter-page-G-1202...
error: unable to delete 'counter-page-G-1202': remote ref does not exist
error: failed to push some refs to 'ssh://git@stash:7999/g/gofra.git'

I'll fix it on this week.

}
}

function Get-MergedLocalGitBranches($ignore)
Copy link
Owner

Choose a reason for hiding this comment

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

Drop unused $ignore?

Copy link
Author

Choose a reason for hiding this comment

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

$ignore was completely removed in 47e6c16.

@dahlbyk
Copy link
Owner

dahlbyk commented May 14, 2015

Revisions from six months ago (ugh) look great. Looking forward to the fix for multiple remotes!

Would be simpler/cleaner to export a single Remove-MergedGitBranches that accepts a -Remote parameter to switch from local deletes to remote (a la git branch -r)?

tkirill added 3 commits May 22, 2015 17:32
I haven't used it in the last 6 month.  This functionality is likely redundant.
"Remote" switches between remote and local branches like `git branch --remote`.  With this switch we don't need to export `Remove-MergedLocalGitBranches` and `Remove-MergedRemoteGitBranches` so we export only `Remove-MergedGitBranches` now.

"All" switches between both remote and local branches and local only.
Default value is "origin".

Also, it is not required to pass PSBoundParameters.  Is works automatically.
@tkirill
Copy link
Author

tkirill commented May 22, 2015

@dahlbyk Your idea about -Remote parameter is good! I implemented it in 31ce862 and it is really better now. Also, I added -All parameter so it is possible to delete both merged and local branches in one call Remove-MergedGitBranches -All.

Bug with multiple remotes fixed in 98956d1. Remove-MergedGitBranches works with origin by default now. There is -RemoteName parameter to switch to another remote. I think an ability to work with multiple remotes in one call by passing an array to -RemoteName is redundant. What do you think?

I've tested different combinations of parameters but not in depth. I used three repositories:

  • origin
  • clone 1 of origin
  • clone 2 of origin with clone 1 as additional remote

I used this script to create this test rig: https://gist.github.com/tkirill/6dd2606d1ae8b9b1e0f5.

Also, I reformatted GitRemoveMergedBranches.ps1 in 7cf9803 basing on the coding style of this project.

foreach ($item in $branches) {
if ($PSCmdlet.ShouldProcess($item, "delete remote branch")) {
Write-Host "Deleting remote branch $item..."
git push $RemoteName :$item
Copy link
Author

Choose a reason for hiding this comment

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

No whitespace escaping for $RemoteName but I think it is safe since it is not possible to use whitespaces in remote's name in Git. @dahlbyk What do you think?

Copy link
Owner

Choose a reason for hiding this comment

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

Correct, no need to handle whitespace in remote name.

@dahlbyk
Copy link
Owner

dahlbyk commented May 23, 2015

-All is a welcome addition, good call.

On the -RemoteName front, it seems like switching to string[] would be pretty simple implementation-wise, without changing the single-remote usage at all. I wonder if a more reasonable default behavior would be to remove all remotes' merged branches (I.e. not filter by remote name)?


Maybe someone more in tune with the Zen of PowerShell can chime in, but it feels like most of the Write-Host here may be better as Write-Verbose? Not sure what the correct way is to implement an actual -Verbose flag, tho.

@tkirill
Copy link
Author

tkirill commented May 23, 2015

@dahlbyk I added support for multiple remotes in 1b881b5. This is really convenient, good that you noticed this!

I wonder if a more reasonable default behavior would be to remove all remotes' merged branches

The only potential problem I can think of is read-only remote. If I fork some project on GitHub I have two remotes:

  • origin -- my fork. I have write access.
  • upstream -- original repository. I need this remote for synchronization. I don't have write access.

I think it is a frequent case and in this case working with all remotes by default doesn't work well. However, we definitely need ability to work with all remotes if we want. I thought to add flag -AllRemotes but we already have -All and these two look confusing together. What if instead of working with all remotes by default we implement these flags:

  • -Remote. Delete only remote branches in origin.
  • -Local. Delete only local branches.
  • -RemoteName. Set an array of remotes' names.
  • -AllRemote. Delete remote branches in all remotes.

Flags -Local -AllRemote delete all possible merged branches. Default behavior without any flags equals to -Local -Remote. It used to work like this before I added -Remote flag yesterday and it was convenient for me. What do you think?

most of the Write-Host here may be better as Write-Verbose

Surprisingly -Verbose already supported by PSCmdlet.ShouldProcess! So I removed Write-Host that duplicated PSCmdlet.ShouldProcess in 0d42639. Result:

Without -Verbose:
no-verbose

With -Verbose:
verbose

@tkirill tkirill force-pushed the DeleteMergedBranches branch 2 times, most recently from 9287c11 to 23e23a2 Compare May 23, 2015 16:03
Default behavior is to delete remote branches in origin only.
@tkirill tkirill force-pushed the DeleteMergedBranches branch from 23e23a2 to 1b881b5 Compare May 23, 2015 16:05
@dahlbyk
Copy link
Owner

dahlbyk commented May 23, 2015

Remote operations being more "destructive" than their local counterparts, how about we remove any sort of default remote behavior?

  • Delete-MergedGitBranches = Delete-MergedGitBranches -Local = delete merged local branches.
  • Delete-MergedGitBranches -Remote= error, not enough information
  • Delete-MergedGitBranches -Remote origin= delete merged remote branches on origin
  • Delete-MergedGitBranches -Local -Remote (git remote) = delete all merged local and remote branches

@tkirill
Copy link
Author

tkirill commented May 24, 2015

@dahlbyk I like this idea! 👍 We have only two flags -Local and -Remote and they allow us to do everything we want. I'll implement this today or tomorrow.

To be honest I don't find removing merged branches in origin dangerous and every time I delete local branches I want to delete them in origin as well. So Remove-MergedGitBranches == Remove-MergedGitBranches -Local -Remote origin would be more convenient for me but I can create alias 👌

P.S. You wrote Delete-MergedGitBranches instead of Remove-MergedGitBranches. Not sure if it is mistype or not :)

@dahlbyk
Copy link
Owner

dahlbyk commented May 24, 2015

Yes, mistyped. Remove is cool.

Dangerousness likely depends on team size and whether or not you use forks - I've dealt with work people doing a "matching" git push --force, so I'm pretty tentative about bulk operations against remotes.

Now there is no default set of remotes.  You have to specify list of remotes manually.

There is `-Local` switch.  If you want to remove only remote branches you can pass only `-Remote string[]`.  If you want to remove local branches too you can pass both parameters: `-Local -Remote string[]`.
@tkirill
Copy link
Author

tkirill commented May 27, 2015

@dahlbyk Take a look at new arguments please 27e75e5.

Surprisingly validation for empty set of remotes' names works automatically. I didn't found this in documentation.

This is how it looks
parameters


I wrote some tests and this test finds another bug with remote branches:

  1. Create repository with one commit to master.
  2. Create another branch branch1. Do not merge it.
  3. Checkout master again.
  4. Clone repository.
  5. Go to clone.
  6. Checkout branch branch1.
  7. Checkout master and merge branch1.
  8. Call Remove-MergedGitBranches -Remote origin.

It will remove origin/branch1 since this branch merged in local master.

I tried to fix this bug in 0f51d0d by checking remote branches against remote-tracking branch corresponding to current branch but this is not enough for the case of multiple remotes.

So I change the way we work with remote branches (again 😕). How it works now:

  • -Remote parameter is replaced by -RemoteBranch parameter. This parameter holds target branches. Not sure -RemoteBranch is a good name. Maybe -RemoteTarget or -Remote are better?
  • For each target branch we search for merged branches from the same remote. Doing so totally separates remotes from each other and from local repository.

Working with remotes is more difficult than I thought 😕 Maybe I just don't see some simple way... What do you think about this new idea?

tkirill added 2 commits May 27, 2015 18:44
…edGitBranches.

If branch merged in remote repository but not merged locally we should remove only remote branch.

After you remove remote branches you should pull changes from remote so the branch becomes merged locally.
Now cmdlet requests target branch in `-RemoteBranch` parameter.  It checks merged branches against target in the same remote as target branch.
@tkirill tkirill force-pushed the DeleteMergedBranches branch from 2e6b470 to 74a3cf5 Compare May 27, 2015 17:13
@tkirill
Copy link
Author

tkirill commented May 31, 2015

Now after few days I doubt the new way is better. Your suggestion about Remote parameter looks fine for me now. Yes there is a possibility of deleting not merged remote branches but at least it is simple. One just need to push to remote before deleting branches. So now I'm think about reverting 0f51d0d and 74a3cf5. Really don't know which one is better.

@dahlbyk
Copy link
Owner

dahlbyk commented May 31, 2015

I'm not really a fan of doing any sort of remote manipulation based on local names because upstream branches don't necessarily have to track a local branch of the same name. I suppose you could remove remote branches that have been merged into the remote branch tracked by the current local branch, if applicable, but that feels like a stretch?

Since we don't specify a reference to the commit we're checking is merged, I can really only think of two reasonable behaviors for Remove-MergedGitBranches -Remote origin:

  1. Remove remote branches that have been merged into local HEAD (i.e. $(git branch -r --merged) -like ' origin/*').
  2. Remove remote branches that have been merged into origin/HEAD (the default branch, typically master but configurable).

Between the two I lean toward the latter, with an error if <remote>/HEAD does not exist (it's typically created by git clone but not git remote add).

More generally, earlier I asked:

General question: would it be useful for any/all of these to accept a parameter (defaulting to HEAD) to identify the ref to check mergedness against...?

Especially for the -Remote scenario the answer seems to be "Yes".

This parameter identify the ref to check mergedness against.
@tkirill tkirill force-pushed the DeleteMergedBranches branch from e016be9 to 7658e2f Compare June 5, 2015 07:21
@tkirill
Copy link
Author

tkirill commented Jun 5, 2015

@dahlbyk I tried to implement -Target parameter in 7658e2f and it looks nice. Here is current interface:

  • -Local -- remove local branches.
  • -Remote origin, other -- set remotes
  • -Target branch -- set target branch. HEAD is default.
  • No parameters equals to -Local

-Target parameter is positional so Remove-MergedGitBranches branch1 removes local branches merged to branch1.

... with an error if /HEAD does not exist ...

I write it as warning since it is not fatal. What do you think? This is how it looks:

head

Also, I added a test with multiple remotes and custom target branch. These cases are currently tested:

  1. Do nothing if no branches: local & remote
  2. Do nothing if branch not merged: local & remote
  3. Remove single merged branch: local & remote
  4. Do nothing if branch is merged but it is a current branch: local
  5. Pass HEAD explicitly: local
  6. Custom branch as a target: local & remote
  7. Multiple merged branches: local & remote
  8. If branch merged locally and not merged remotely then remove only local branch: test
  9. If branch merged remotely and not merged locally then remove only remote branch: test
  10. If branch merged remotely and locally then remove both local and remote branch: test
  11. Multiple remotes:

@dahlbyk
Copy link
Owner

dahlbyk commented Jun 5, 2015

No time to review code now, but API seems just right and test coverage is impressive! Will try to revisit over the weekend - bug me next week if I don't.

Thanks for iterating on this!

@tkirill
Copy link
Author

tkirill commented Jun 13, 2015

bug me next week if I don't.

@dahlbyk 👋

@tkirill
Copy link
Author

tkirill commented Nov 18, 2015

Just discovered one more corner case.

If we have branch which points at the same commit as develop then this branch considered merged and the script removes it. However, it can be just new branch that has no commits yet. Removing such branch surprised my colleague who created this branch 😆

I don't know if we could fix this since the same situation can occur with fast-forward merge.

Also, after few month of using current Remove-MergedGitBranches I have feeling that its interface isn't convenient. Maybe it isn't true but it would be good to review it again.

@rkeithhill
Copy link
Collaborator

Closing this in favor of PR #663.

@rkeithhill rkeithhill closed this Feb 24, 2019
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.

Remove remote merged branches command
3 participants