{"thread":{"id":"62591","subject":"Detecting squash-merged branches (and question about git-diff-tree)","startedAt":"2024-12-03T14:03:42Z","lastAt":"2024-12-03T17:43:05Z","messageCount":5,"participants":["Max Gautier","Kristoffer Haugsbakk"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"508525","messageId":"Z08N4AlQKiNi-IOI@framework","threadId":"62591","inReplyTo":null,"subject":"Detecting squash-merged branches (and question about git-diff-tree)","fromName":"Max Gautier","fromEmail":"mg@max.gautier.name","sentAt":"2024-12-03T13:55:44Z","receivedAt":"2024-12-03T14:03:42Z","isPatch":false,"sender":{"key":"mg@max.gautier.name","avatar":"https://avatars.githubusercontent.com/u/13346812?v=4"},"body":"Hi,\n\nI tend to work on project which do a lot of \"squash-merging\" e.g, merge\nbranches by having a robot squash the branch in a new commit on top of\nthe main branch.\n\nThis makes it a bit hard to remove my branches when they are\n\"squash-merged\" (in contrast to using `git branch --merged`)\n\n\nI started a little script to detect such branches; initially I used git\ncherry, but this only detect the case where the branch has 1 commit,\nwhich is not enough.\n\nSharing below if anyone is interested and/or want to give some feedback\n(warning, this is probably full of bash-ism/gnu-ism).\n\nWhich leads me to my actual question:\nI wanted to use diff-tree in --stdin mode (instead of calling it\nrepeatedly in a loop), feeding it my target branch and\nthe list of relevant commits, but apparently --merge-base and --stdin\nare mutually exclusive. What's the reason for that ?\nI suppose it's related to the 3 possible line forms diff-tree accepts in\n--stdin mode, but I didn't find a spelled out explanation in the\noriginal thread implementing --merge-base [1].\n\nIs there another alternative for computing the patches ids of branches\nin that way ? A '%(mergebase)' token for git for-each-ref would also\nwork, but there is no such thing either that I know of. \n\n(Of course, the script as such runs ~reasonably~ well, but it does spend\n95% of it's time waiting for subprocess, which bugs me a little^^)\n\nThanks for reading me !\n\n[1]: https://lore.kernel.org/git/cover.1599332861.git.liu.denton@gmail.com/\n\n---\n\n#!/bin/bash\n# $1 : target ref (in which we search for squashed branches)\n# (default: upstream/HEAD)\n# ${@:2} (all scripts args after the first one): git for-each-ref\n# patterns for refs candidates for squash-merge detection\n# (default: refs/{remotes/origin,heads}/}\n\ndeclare -A commit_by_patch_ids\noldest_merge_base=${1-upstream/HEAD}\nref_patterns=${@:2}\nref_patterns=${ref_patterns:-refs/remotes/origin/ refs/heads/}\n\nfor ref in $(git for-each-ref ${ref_patterns} \\\n                --format='%(objectname)' \\\n                --no-merged=${1-upstream/HEAD} )\ndo\n  patch_id=( $(git diff-tree -p --merge-base ${1-upstream/HEAD} $ref \\\n              | git patch-id --stable) )\n  commit_by_patch_ids[$patch_id[0]]=$ref\n  # Caveat:\n  # It's possible for different commit to have the save patch-id\n  # (for instance on a feature branch do: git checkout feature;git branch\n  # old;git rebase main -> old and feature would probably have the same\n  # patch-id, if I understand this correctly)\n  # proper treatment of this would need to use array of commits by\n  # patch-id, but bash does not support multidimensional arrays.\n\n  # Check oldest commit we will need to go back to when checking if a\n  # patch-id exist in the source branch.\n  # This assumes that branches are not squash-merged before their fork\n  # point.  This avoids going back all the way to the first commit,\n  # which can be prohibitively expensive on repository with a long\n  # history (e.g, linux kernel tree takes 13 minutes on a recent machine\n  # for git log -p | git patch-id)\n  oldest_merge_base=$(git merge-base $oldest_merge_base $ref)\ndone\n\ndeclare -a squashed\n# Extract commits whose patch-id exist in the target branch.\n#\nfor patch_id in $(git log -p ${oldest_merge_base}..${1-upstream/HEAD} \\\n                 | git patch-id --stable | cut -d ' ' -f 1)\ndo\n    if [[ -n \"${commit_by_patch_ids[$patch_id]+exists}\" ]];then\n        squashed+=(\"--points-at=${commit_by_patch_ids[$patch_id]}\")\n    fi\ndone\n\nprintf \"%s\\n\" \"$(git for-each-ref $ref_patterns \\\n                    --format='%(refname:short)' \\\n                    ${squashed[@]})\"\n\n-- \nMax Gautier\n"},{"id":"508541","messageId":"75269425-49ee-4a16-beef-614da392d528@app.fastmail.com","threadId":"62591","inReplyTo":"Z08N4AlQKiNi-IOI@framework","subject":"Re: Detecting squash-merged branches (and question about git-diff-tree)","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2024-12-03T15:30:53Z","receivedAt":"2024-12-03T15:31:14Z","isPatch":false,"sender":{"key":"kristofferhaugsbakk@fastmail.com","avatar":null},"body":"Hi Max\n\nOn Tue, Dec 3, 2024, at 14:55, Max Gautier wrote:\n> Hi,\n>\n> I tend to work on project which do a lot of \"squash-merging\" e.g, merge\n> branches by having a robot squash the branch in a new commit on top of\n> the main branch.\n>\n> This makes it a bit hard to remove my branches when they are\n> \"squash-merged\" (in contrast to using `git branch --merged`)\n>\n>\n> I started a little script to detect such branches; initially I used git\n> cherry, but this only detect the case where the branch has 1 commit,\n> which is not enough.\n\nI’m working on a program/script to detect merged/squashed/rebased (or\ncherry-picked) refs.  Is it okay if I contact you if I finish it? ;)\n\nThere’s also this program which says it can do the same thing (not tried):\n\nhttps://github.com/foriequal0/git-trim\n\nMy program tries to be like a for-each-ref for merged etc. refs.\nMore focused on reporting first since that can then be used for\ndeletion or whatever else.\n"},{"id":"508543","messageId":"Z08o-O5eiu_ZsYrp@framework","threadId":"62591","inReplyTo":"75269425-49ee-4a16-beef-614da392d528@app.fastmail.com","subject":"Re: Detecting squash-merged branches (and question about git-diff-tree)","fromName":"Max Gautier","fromEmail":"mg@max.gautier.name","sentAt":"2024-12-03T15:51:20Z","receivedAt":"2024-12-03T15:51:22Z","isPatch":false,"sender":{"key":"mg@max.gautier.name","avatar":"https://avatars.githubusercontent.com/u/13346812?v=4"},"body":"> I’m working on a program/script to detect merged/squashed/rebased (or\n> cherry-picked) refs.  Is it okay if I contact you if I finish it? ;)\n\nFeel free to, yes\n\n> \n> There’s also this program which says it can do the same thing (not tried):\n> \n> https://github.com/foriequal0/git-trim\n> \n> My program tries to be like a for-each-ref for merged etc. refs.\n> More focused on reporting first since that can then be used for\n> deletion or whatever else.\n\nHum, I'm wondering why it didn't find this earlier, I did search though.\nIt apparently use git commit-tree to create a temporary commit, and git\ncherry on that to check if it's in the target branch.\nI wonder if this wouldn't cause problems in case of conflicts though,\nduring the git commit-tree process ?\n\n-- \nMax Gautier\n"},{"id":"508544","messageId":"Z08vUxoyK67LXZc3@framework","threadId":"62591","inReplyTo":"Z08N4AlQKiNi-IOI@framework","subject":"Re: Detecting squash-merged branches (and question about git-diff-tree)","fromName":"Max Gautier","fromEmail":"mg@max.gautier.name","sentAt":"2024-12-03T16:18:27Z","receivedAt":"2024-12-03T16:18:29Z","isPatch":false,"sender":{"key":"mg@max.gautier.name","avatar":"https://avatars.githubusercontent.com/u/13346812?v=4"},"body":"Uh, btw, there is a typo in the script in the previous email, it should\nactually be:\n\n commit_by_patch_ids[${patch_id[0]}]=$ref\n\non line 20 (instead of $patch_id[0])\n\nNot sure how I ended up sending the wrong version.\n\n-- \nMax Gautier\n"},{"id":"508549","messageId":"95b88ece-8188-46aa-b263-61bb371e4293@app.fastmail.com","threadId":"62591","inReplyTo":"Z08o-O5eiu_ZsYrp@framework","subject":"Re: Detecting squash-merged branches (and question about git-diff-tree)","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2024-12-03T17:42:43Z","receivedAt":"2024-12-03T17:43:05Z","isPatch":false,"sender":{"key":"kristofferhaugsbakk@fastmail.com","avatar":null},"body":"On Tue, Dec 3, 2024, at 16:51, Max Gautier wrote:\n>> My program tries to be like a for-each-ref for merged etc. refs.\n>> More focused on reporting first since that can then be used for\n>> deletion or whatever else.\n>\n> Hum, I'm wondering why it didn't find this earlier, I did search though.\n> It apparently use git commit-tree to create a temporary commit, and git\n> cherry on that to check if it's in the target branch.\n> I wonder if this wouldn't cause problems in case of conflicts though,\n> during the git commit-tree process ?\n\nMaybe, I haven’t tried.  But using something that creates a commit in\nthe DB seems a bit unnecessary/excessive in any case.\n\nYour diff approach seems more direct.\n\n-- \nKristoffer Haugsbakk\n\n"}]}