3

This question is similar to git diff - only show which directories changed.

Let's say I have the following project structure:

src
  - project.sln
  - Project1
    - Foo.cs
    - Bar.cs
    - Project1.csproj
  - Project2
    - Fizz.cs
    - Buzz.cs
    - Project2.csproj
  - Project3
    - HelloWorld.cs
    - Project3.csproj

Now, let's say that I update Foo.cs, Bar.cs and Fizz.cs. This means that src/Project1 and src/Project2 have changes in them.

In this case, I would like to be able to ask git to return the paths of src/Project1/Project1.csproj and src/Project2/Project2.csproj.

I suppose it would also be good to only get the root dir of each project (src/Project1/ and src/Project2).

How can I accomplish this with git and bash?


For those that feel like this is an xyproblem, you are correct. The problem I am having is that dotnet format (which formats your .NET code) needs to analyze the entire solution, just to format 2 files (for example).

By knowing the exact projects that change, I could instruct dotnet format to only format those project instead, hopefully improving performance.

2
  • dotnet format has an --include option to specify the files or folders that should be formatted. Did you consider this already? With this you could always run it against the whole solution but only include the modified & added files from git status. Commented Aug 2, 2022 at 11:02
  • That is exactly what I do right now. But if you were to look at the logs from dotnet format, you'd see that it will still build and analyze projects unrelated to your changed files because it needs to build to run analyzers to format code. This results in my format taking +1 minute.. i want to work around this by only calling format on the specific projects that were changed to save some time. Commented Aug 2, 2022 at 11:20

2 Answers 2

1

Check dotnet-affected. Probably that's what you need.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, I think that's exactly what I was looking for!
1

It took me a few readings to figure out what you were really asking, but I think I finally have it now:

  • We want to find added all or modified files (deleted files are probably irrelevant? type-change, from or to symbolic link, probably can't happen, etc).
  • But, we want to toss out, from this list, any files that don't exist in a directory that itself contains or lives underneath a directory that contains a *.csproj file.

That is, if the diff listing said that README.cs were updated in the top level, that would be irrelevant unless there were a top-level *.csproj file.

Now, we know that in general, the tool that will tell us which files are changed between any two individual commits is git diff (or for scripting purposes, git diff-tree). For instance, git diff parent child will tell us about the various files that are changed. Using --name-status gets us the path names and status letters.

Adding a diff filter for A and M, and making sure we control rename detection so that we either don't get R at all, or only get it for 100% matches, allows us to drop the "status" part, i.e, just --name-only. If we need to handle weird characters in file names, we can add -z and use \0 terminators with bash's read -r -d $'\0' construct (or -d ''; I like to write the explicit zero) as well.

Now, there's a general problem here: just because some *.cs file has changed doesn't mean the corresponding *.csproj file has also changed (well, presumably it doesn't). So we can't inspect the diff output for .csproj files. Unfortunately, to see whether there's a *.csproj file, the only direct tool we have here is git ls-tree -r --full-tree child, and it does not accept pathspec arguments, so we cannot ask about **/*.csproj for instance.

But there's a useful trick: valid *.csproj files are presumably always non-empty. Using git grep -l with the matching expression . will let us find such files. We can supply a commit hash ID here and then strip it from the output:

git grep -l --full-name . $hash -- "**/*.csproj" | sed "s/$hash://"

for instance.

Note that git grep is porcelain, like git diff, but it seems that -l defeats most of the options we'd have to worry about, and we want --full-name anyway here. To use git diff-tree, just add -r --no-commit-id. Rename detection is off in git diff-tree unless explicitly enabled.

That is, given the start and end commit hash IDs, or just HEAD:

git diff-tree -r --no-commit-id --name-only --diff-filter=AM HEAD -- "**/*.cs"

(If you use HEAD with git grep -l you'll want to strip a literal HEAD: from the output.)

Turning all this into a useful bash script will need a bit more work: you must run the git diff-tree, read its output, pick out the various directories, and compare with the git grep output's list of *.csproj-containing directories.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.