RamblingCookieMonster / BuildHelpers

Helper functions for PowerShell CI/CD scenarios
MIT License
214 stars 47 forks source link

Get git changed file #111

Closed FISHMANPET closed 4 years ago

FISHMANPET commented 5 years ago

Lotta changes here, all for fixing #106 plus a bunch of other QOL stuff I found along the way.

This changes Get-GitChangedFile to use git diff instead of git diff-tree. I intitally sought to perfectly replicate the functionality as existed before, but once I got into the tests file I see that it never worked the way you intended it, so it's essentially been in a bug state for the last 3 years. So this introduces a breaking change because running it without parameters will return files whereas it didn't before, but I think that was the original intention.

(By the way, the issue was that by default diff-tree hides merge commits so you'd never see commits that were merged in.)

So let's consider this fixing that unfiled issue.

Second, I add the ability to get way more specific about what changes you're looking for

RightRevision, LeftRevision, and RangeNotation

git diff can be used to find changes in a range. My use case as mentioned in #106 was seeing how I'm ahead of master. The git command for that looks like this: git diff --name-only origin/master... That's the same thing as using Compare in Github, which is what I was trying to emulate (if there's nothing before or after the triple dots it implies HEAD, so that's the same as origin/master...HEAD). That translates into this command: Get-GitChangedFile -LeftRevision "origin/master" RangeNotation defaults to triple dots so I don't need to specify it. Another option for RangeNotation is double dots. Triple dot is a one way diff, it shows you the ways that the 2nd revision is ahead of the 1st revision, so my example only shows me changes where I'm ahead of master, but not where I'm behind master and there are changes from master to merge into my branch. Double dot is a two way diff, it shows you the differences between the two, so it would show you what is in the 2nd revision but not the 1st, as well as what's in the 1st but not the 2nd. So origin/master..HEAD would be equivalent to HEAD..origin/master

If I wanted to see all the changes in master that I need to merge into my branch, aka how I'm behind master, I could use this: Get-GitChangedFile -RightRevision "origin/master" which is the equivalent of ...origin/master or HEAD...origin/master

I thought about adding some validation here because of all the ways you can specify a revision I don't think there's a way to do it, if you pass garbage in then you'll get errors from git about invalid paths and nothing will be returned.

DiffFilter

With git diff you can also specify a diff filter so that only certain changes are returned. This parameter just tacks on whatever you give it to --diff-filter= and adds it to the diff command. I've explained the options in the help file, but as an example `Get-GitChangedFile -DiffFilter "AMR" would only return files that have been Added, Modified, or Renamed. I also added parameter validation here so you can only pass in valid characters to diff filter.

Include/Exclude

Previously you could specify both an Include and an Exclude, but if you did that only the Exclude would be executed. I looked at using parameter sets to make them mutually exclusive but it turned out it was easier for me to make them both work. So now if both an Include and Exclude filter are specified, if will first get all files that match the Include Filter, then it will remove all files that match the Exclude filter. This is another breaking change though I think it's a bug if people were specifying both and expecting both to have an effect

Commit/Default

If Commit is given, we now pass $Commit^! as the revision, which is how to specify just that single revision. It will show you what changed in that commit, and will return all the changes that happened when a merge commit was done. If nothing is specified it will use HEAD^! which is just the current revision.

Raw Revision

All the options above are used to construct a revision string to eventually pass to git diff. Well maybe there are even more advanced cases that aren't covered by the code above, or someone wants the flexibility to generate their own revision string and pass it in. Well this let's you do that. An undocumented feature here is that since this is just passing a string into git diff you can also use this to pass additional parameters to git diff which has a very high likelihood of breaking your git command. Since git diff can't do anything just read data, you're only hurting yourself. I wouldn't document this or call it a feature, but if someone stares at the code long enough they'll figure it out. This was the case with your previous version, since you were just passing the commit to git diff-tree, if you specified a "commit" with options in it they'd get passed to the command.

Handling not in a Git Repo

The previous code didn't properly fail if you weren't in a repo, because if you weren't in Git Repo the commands to get $GitPathRaw would fail and then $GitPath would be empty because the Resolve-Path would fail since $GitPathRaw was empty, and then Test-Path $GitPath would fail because $GitPath was empty and so the if loop wouldn't execute, so you'd never get to the throw statement. I put the initial setting of $GitPathRaw in a try-catch block and throw a fatal error if git rev-parse fails (there doesn't appear to be anyway to get Git to tell you you're not in a repo, everything I found said you should intercept the error from that command).

I also modified the warning at the end if no files are returned. Previously it hinted that maybe you weren't actually in a git directory but I think with the tests at the top it's not possible to get to the bottom if you're not in a Git repo. It's also possible now with the additional flexibility that no files will match your criteria. So I left the warning that no files were found, but I made it less scary since finding no files doesn't mean there was actually an error.

Invoke-LikeFilter FilterReplace

The filtering in Get-GitFileChanged happens before any powershell path modification, so the filenames are just strings. Git uses Unix slashes (/) instead of the Windows (\). This is something you may not realize and if you're trying to filter on a directory and using the Windows slash it won't be have like you expect. So I added a $FilterReplace parameter, which takes a 2 element array, and if specified, for every filter specified in the $FilterArray it will replace all occurances of the first element in the replace array with the second, using the .Replace() method. I then modified Get-GitChangedFile to pass the parameter -FilterReplace '\','/' into Invoke-LikeFilter so even if your filters specify Windows slashes it will still match the Unix style slashes from Git.

Tests!

I added tests! My change fixed the commented out test for Get-GitFileChanged which tells me what I've done was your intended behavior. I also added tests for nearly all the extra filters I added. The one I couldn't check in the repository was using -RightRevision because there was no commit that I could guarantee would be ahead of HEAD (aka no commit that would be guaranteed to have changes that haven't been merged into HEAD). This could be artificially created if you made a branch, made some changes to it, but never merged it into master. I'm not sure if you could delete that branch, as that might also delete the commits. I also added tests for Invoke-LikeFilter since there were none before.

FISHMANPET commented 4 years ago

Pushed a slight update to this dealing with revision strings, HEAD^! was not detecting changes in a merge commit, so I updated the diff command: For no parameters, the diff string is HEAD^..HEAD instead of HEAD^! For specifying a specific commit, the diff string is "$Commit^..$Commit" instead of "$Commit^!" (<rev>^ means the parent of <rev>). Also added a positional parameter so you can run Get-GitChangedFile <rev> without a parameter to get the changes in the specific commit <rev>

FISHMANPET commented 4 years ago

Now that this PR is over a year old, I'm wondering if you have any interest in this one way or the other. There hasn't been a whole lot of movement on this project, do you need any help maintaining it?

RamblingCookieMonster commented 4 years ago

So sorry! Kept coming back but not having the motivation to read through all the changes or the switch to git diff. lgtm on a quick review, and no one has had any input, so here we go! Thanks for all this work!