When doing bigger refactoring in dynamic language (Javascript), I normally end up doing a lot of searching around to find all the affected parts, and I write them down in a checklist to be able to track them. I do this in mostly BFS manner. Then, refactoring itself I do in more of a DFS manner, meaning I follow the checklist but I also dig deeper when I realize for certain item that there is more work to get done.
Finally, if code is well tested, I expect broken tests to further guide me and warn me about all the places that I missed. If it is not well tested, well then it comes to manual testing of course, and hopefully I add some missing tests on the way.
That said, I just recently switched from JS project to Haskell project, and experience is very much different. I don't have to do so much of investigating and thinking, I just start with one change and let the compiler guide me -> when I am done with fixing all compiler errors, I am mostly done all together + I have a good idea of all the affected parts (I don't need to search through the codebase that much). Then there are tests again to check the logic is functioning ok (not covered by type system), and that is mostly it.
What do you mean by activity as a whole reflecting in commits: is that in case where you did the change through multiple commits and want it to be obvious they are all part of that change?
Personally, I always create new branch for new "feature/activity", and when I am done, I squash all commits into one commit and rebase that onto master, often with github issues number included in the commit message.
If feature/activity is bigger and it makes sense to keep it split into multiple commits (does not happen often to me), I still just rebase it into master - usually commit messages are descpritive enough to make it obvious what is the relationship between these commits. I guess you could prefix all of the commit (their messages) with same prefix, smth like issue number of feature name, to make that more obvious if really needed?