Fossil Forum

Can I squash commits?
Login

Can I squash commits?

Can I squash commits?

(1) By patmaddox on 2023-04-15 20:52:18 [link] [source]

I'm 99% sure Fossil's structure allows this, but I don't know if it's implemented...

I would like to take a series of commits and squash them so they appear as a single commit on the timeline. This would allow to get the best of two common workflows from git: tiny commits, and squash commits.

The difference is that in git, you have to pick which one you want. If you squash a commit, you lose the history that created it. If you use tiny commits, you end up with a verbose timeline.

So, let's say I've got commits:

...
ghi789
def456
abc123
...

I want to create a new squash commit that appears as a single commit in the timeline. This would allow for a tidy timeline, but still let me drill down into the individual commits of the squash.

Can fossil already do that, or something like it?

(2) By Stephan Beal (stephan) on 2023-04-15 21:03:11 in reply to 1 [link] [source]

I'm 99% sure Fossil's structure allows this, but I don't know if it's implemented...

The short answer is no - fossil is very much designed to record exactly what happened, as opposed to what the user may have retroactively preferred to have happen.

This would allow for a tidy timeline

But would not reflect reality, and so goes against fossil's design.

(4.1) By patmaddox on 2023-04-15 22:14:30 edited from 4.0 in reply to 2 [link] [source]

But would not reflect reality, and so goes against fossil's design.

I disagree. The timeline still has all of the information. The new commit is just a summary, with the squashed commits providing more detail. It's the equivalent of this conversation:

"What did you do on Friday?"
"I implemented the thing" (summary)
"Cool - how did you do that?"
"I did foo, bar, baz" (detail)

The only real piece missing at this point is that fossil info won't show the combined diff of the commits.

To me, this is part of the exciting power of fossil. A lot of the workflow stuff that people do in git is to work around its relatively weak presentation abilities. But fossil has data structures that allow you to decouple the data from the presentation if you want - but the data is always there.

(6) By Andy Bradford (andybradford) on 2023-04-16 04:13:40 in reply to 4.1 [link] [source]

> The new commit is just a  summary, with the squashed commits providing
> more detail

I've never understood the need for "squash commits" and indeed they seem
to be more of a crutch and impediment than any useful thing.

Put all your commits for a given feature in a branch, and when you merge
to the "main line" or trunk, then  that single commit for the merge will
have all the changes "squashed" into it and you can put whatever comment
you want on it.

Then when  you view the timeline,  just append &r=trunk to  restrict the
timeline view to just the contents of trunk (well, mostly).

I think  the problem with  "squash commits" is  that they muddle  up two
different concepts, the "commit" and the "timeline" view of the sequence
of commits.

Andy

(7) By Andy Bradford (andybradford) on 2023-04-16 04:19:15 in reply to 6 [link] [source]

> Then when you view the timeline,  just append &r=trunk to restrict the
> timeline view to just the contents of trunk (well, mostly).

For  example, consider  the difference  between  the two  views where  I
"squash" out the commits from the json-settings-command branch and limit
it to just the branch and the merge commits:

https://www.fossil-scm.org/home/timeline?c=c7221a2e8c8cc175&y=a

https://www.fossil-scm.org/home/timeline?c=c7221a2e8c8cc175&y=a&r=trunk

Andy

(19) By Marcelo Huerta (richieadler) on 2023-07-03 15:31:08 in reply to 6 [link] [source]

I've never understood the need for "squash commits" and indeed they seem to be more of a crutch and impediment than any useful thing.

Put all your commits for a given feature in a branch, and when you merge to the "main line" or trunk, then that single commit for the merge will have all the changes "squashed" into it and you can put whatever comment you want on it.

I think the "squash commit" request is so frequent because in most cases in Git the "merge" just consists in "fast-forwarding" all the individual commits to the selected new branch. When you merge in Fossil you always get a single commit with the direct merge. (In Git parlance, in Fossil all merges are squash merges.)

(20) By Konstantin Khomutov (kostix) on 2023-07-04 10:08:36 in reply to 19 [link] [source]

in most cases in Git the "merge" just consists in "fast-forwarding" all the individual commits to the selected new branch

I beg to disagree: most workflows I was (and am) involved with use "normal" merges where a merge ends up being a commit with two or more parents.

I think the OP has summarized their rationale pretty well:

The only real piece missing at this point is that fossil info won't show the combined diff of the commits.

I do know teams which use Git and which do use squash commits partially for this reason.
The other reason is that some teams opt to not rely on rebase+force push iterations when a merge/pull request is being reviewed and fixed as suggested by the reviewer(s), with the logical outcome being that each MR/PR ends up consisting of a series of "initial" commits plus one or more series of "fix after review"-style of commits on top of that. Instead of merging the resulting series "as is" or properly massaging it to form an "ideal history" (a typical approach in high-profile projects using Git, frowned upon in Fossil's mindset) some of the teams opt for squash-merging where the whole resulting series is lumped into a single commit.

IMO this is the worst of both approaches (Git's "ideal history" and Fossil's "precise record of history") but oh well… ;-)

Back to the OP's rationale, I think that if the problem exists, it's pretty much a problem with the UI which should not be worked around by creating nonsensical history.

(21) By patmaddox on 2023-07-29 19:28:56 in reply to 6 [link] [source]

Put all your commits for a given feature in a branch, and when you merge to the "main line" or trunk, then that single commit for the merge will have all the changes "squashed" into it and you can put whatever comment you want on it.

Just to follow up on this... this suggestion finally clicked for me. I see that the merge commit has the entire diff. Also, I love that Fossil lets me amend commit messages after the fact.

So this workflow is quite nice: work on a branch, and then when there's a solid chunk of work I want to bring in, merge it and summarize in the merge commit.

Example: https://patmaddox.com/info/747f92173fc13efe

(15) By v (verachan) on 2023-05-04 18:56:19 in reply to 2 [link] [source]

But would not reflect reality, and so goes against fossil's design.

Can I get a link to an FAQ about this?

(16.1) By Warren Young (wyoung) on 2023-05-04 23:57:29 edited from 16.0 in reply to 15 [link] [source]

Since the argument typically comes up in contrast with Git, the most relevant reference is §2.7 of the Fossil vs Git document, "What you should have done vs. What you actually did".

That links you to our Rebase Considered Harmful doc, where you find §6.1, "Individual check-ins support mutual understanding."

Less philosophical, more practical is our Git to Fossil Translation Guide, where we don't currently discuss squashing, but there is a good discussion on its inverse, "There Is No Staging Area."

All of these documents are worth reading from start to end, especially if your wish is to understand how and why Fossil is different from Git.

(3.1) By patmaddox on 2023-04-15 22:13:37 edited from 3.0 in reply to 1 [link] [source]

Alright, I figured out how to do it - at least with fossil ui. Below script demonstrates it. I just hide the commits I don't want to show up, and create a new commit that references them.

I have a few questions:

  1. What is the --raw option in fossil tag? The explanation doesn't really provide any detail.
  2. How can I see that a commit has been tagged with --raw hidden? fossil info doesn't show that for a commit. I have to fossil tag list --raw hidden to find commits with it - I'd like to look at a commit to see if it has it.
  3. How do I get fossil timeline to not show the hidden commits? fossil ui is hiding them, like I want.

#!/bin/sh
workdir=$(mktemp -d)
echo "workdir: ${workdir}"
fossil new ${workdir}/squash.fossil > /dev/null
fossil open ${workdir}/squash.fossil --workdir ${workdir} > /dev/null

cd ${workdir}

add_commit()
{
    echo $1 > $1
    fossil add $1 > /dev/null
    echo $(fossil commit -m "add $1" | grep New_Version: | awk '{print $2}') | cut -c 1-6
}

foo=$(add_commit foo)
bar=$(add_commit bar)
baz=$(add_commit baz)

echo "BEFORE squash"
fossil timeline -t ci

cat > message <<EOF
Nice and clean

squashes:
[$baz]
[$bar]
[$foo]
EOF

fossil commit --allow-empty -M message > /dev/null
for commit in $foo $bar $baz; do
    fossil tag add --raw hidden $commit #> /dev/null
done

echo "AFTER squash - timeline still shows hidden commits, ui hides them"
fossil timeline -t ci

(8) By Stephan Beal (stephan) on 2023-04-16 12:42:14 in reply to 3.1 [link] [source]

What is the --raw option in fossil tag? The explanation doesn't really provide any detail.

It's a "power user" feature without many practical uses. Normally, when you add a tag it's a "symbolic tag," which means:

  • The tag name X is actually stored as sym-X.
  • That name can be used in place of a version in almost all contexts.

Non-symbolic ("raw") tags have neither of those properties.

How can I see that a commit has been tagged with --raw hidden?

The "whatis" command will list certain tags for an artifacts, one of which is "hidden". Other than that, i'm not aware of a straightforward way to do it, nor a way to easily list arbitrary tags for a given artifact, because use of tags in fossil has never been developed to that point.

In practice, "symbolic tags" cover 99% of what people want to do with tags so there's never been much justification for developing features for non-symbolic tags. The data model supports them, and there are interesting uses for them (e.g. locking of forum threads), but such features require new code and new code requires a volunteer interested in writing in maintaining that code.

How do I get fossil timeline to not show the hidden commits?

The CLI timeline doesn't support that, for the same reason as above: nobody who needs that feature has yet volunteered to write it.

FWIW/sidebar: i'm one of the folks who sees no utility in hiding information in the timeline, and in 15+ years of using fossil heavily have never once needed it. What exactly should be hidden is always highly subjective and fossil is a tool for open collaboration. What you want to hide is almost certainly not what J. Random Collaborator would want to hide, making hiding a questionable practice and a hindrance to collaboration.

(10) By patmaddox on 2023-04-16 20:08:19 in reply to 8 [link] [source]

What exactly should be hidden is always highly subjective and fossil is a tool for open collaboration. What you want to hide is almost certainly not what J. Random Collaborator would want to hide, making hiding a questionable practice and a hindrance to collaboration.

This is one reason I like the summary idea - it doesn’t hide anything, it just raises the level of abstraction. Then you can drill down further if you like, or view without summaries, to see all the details.

I applied some hidden tags and quickly undid them because I wanted the timeline to have all the info. But, I would also like some control over the granularity of that info.

(5) By patmaddox on 2023-04-16 02:33:15 in reply to 1 [source]

I think a simple, straightforward implementation of this would be called a "summary", where you define a summary by a range of contiguous commits.

I'm unsure of whether you tag the first and last commit of the summary, or if there's a new commit with tags on it, or a new object type. Probably tagging the ends of the commit range is simplest. Can someone with knowledge of fossil internals weigh in?

Summaries can be nested, but not overlap.

basic version

c5
c4 summary:foo
c3
c2 summary:foo
c1

would display as:

c5
foo
c1

I would want foo to appear as a commit, with its own message.

nested

c7
c6 summary: bar
c5 
c4 summary:foo
c3
c2 summary:foo
c1 summary:bar
c0

would display at top-level as:

c7
bar
c0

and then can be expanded to

c7
c6
foo
c1
c0

invalid: overlapping

c7
c6 summary: bar
c5 
c4 summary:foo
c3
c2 summary:bar
c1 summary:foo
c0

I think this should be invalid, as there's just not a good way to handle it and keep things simple. But, maybe someone else can think of something. Similarly, I think non-contiguous commits should not be summarized.

(9) By Stephan Beal (stephan) on 2023-04-16 13:03:59 in reply to 5 [link] [source]

I'm unsure of whether you tag the first and last commit of the summary, or if there's a new commit with tags on it, or a new object type. Probably tagging the ends of the commit range is simplest. Can someone with knowledge of fossil internals weigh in?

New artifact types are inherently incompatible with any older fossil versions, and must be structured such that their type can be unambiguously distinguished from that structure (see src:/doc/trunk/www/fileformat.wiki. The range of specific artifact types is limited because each is required to be structurally unambiguous. (There is no info in an artifact which tells fossil what type it is - the type is inferred from its structure.) Thus new artifact types are exceedingly rare: the only new type added since fossil's earliest days was forum posts.

Getting a new artifact type included would require a compelling, SCM-relevant use for it. A new artifact type for specifying what information to hide from the timeline would be way out of scope, whereas new conventions for use of tags for that purpose would not be out of scope.

In the latter case, a so-called control artifact could be used which defines a list of checkins to include as a summary for a given checkin. Specifying a range of checkins would not strictly be correct because it is possible (though rare, and only useful in highly unusual and undesirable cases), to change the parentage of a checkin, which would change the meaning of the range. That is, what is contiguous today might not be contiguous a few minutes later. Also, the overall tag semantics would enable future manipulation of that list (which would arguably be a feature rather than a bug).

As to the utility of your proposal, i'm ambivalent. It's not a feature i'd use, so not one i'd volunteer to implement, but i look forward any debate on the topic from those who feel strongly one way or the other.

(11) By patmaddox on 2023-04-16 20:08:44 in reply to 9 [link] [source]

Thanks so much for this info, it’s super helpful.

it is possible (though rare, and only useful in highly unusual and undesirable cases), to change the parentage of a checkin, which would change the meaning of the range. That is, what is contiguous today might not be contiguous a few minutes later.

Hrm… how? I was under the impression that this was strictly not possible in Fossil. Given that I’d be the one taking a stab at implementing this, sounds like I need to understand this uncommon case.

the overall tag semantics would enable future manipulation of that list (which would arguably be a feature rather than a bug).

Absolutely, you should be able to amend a summary, which would be its comment and list of tags.

(12) By Stephan Beal (stephan) on 2023-04-16 21:56:32 in reply to 11 [link] [source]

it is possible (though rare, and only useful in highly unusual and undesirable cases), to change the parentage of a checkin

Hrm… how? I was under the impression that this was strictly not possible in Fossil.

The "reparent" command adds a tag which effectively says "the parent of check-is X is now check-in Y." It's not modifying the underlying data (that's 100% immutable), it's just "amending" the public record via a well-defined use of a specific tag. This sort of amending is possible because the underlying SCM-relevant data and its presentation (like the timeline) are separated, which allows fossil to do things like wedge in "changes" between the actual data and how it's rendered to users without actually modifying the original data. This document goes into that aspect of the data model in more detail.

In any case, never reparent anything. It's primarily intended to patch the project history when a given check-in is "shunned" (forcibly removed from the repository by a superuser), so that any check-ins which derive from it can be told to point to the previous ancestor instead. "Shunning" is fossil's only mechanism for actually modifying project history, and only in the sense of outright deleting it from the history. Any references to that content's hash (e.g. in a subsequent check-in) still continue to exist, and reparenting is a workaround which allows such "holes" in the project's history to be bridged over.

That said, shunning is a feature largely dissuade people from making any use of. It "really should" only be used to remove harmful, illegal, or otherwise compromising content from a repository, and never for "cleaning up."

(13) By Ben (bvautier) on 2023-04-18 03:01:06 in reply to 9 [link] [source]

I think a timeline tag view could elegantly resolve this itch.

It's something I would find useful as well.

Instead of the timeline showing every commit, there would be an option to only display tags.

The concept of the propagation tag lends itself nicely to this. Fossil could collect all the commit messages that have the same tag and display them in a tooltip if the user hovers over the tag.

We don't need to add a new artifact. It's just a new view. (A new option on the timeline page.)

Each tag will represent multiple commits (according to the existing rules found in the docs), which will make the timeline a lot more compact and easier to grasp.

It provides us with the best of both worlds. An immutable history with a visually pleasing timeline. (Which is really the only thing most people are after. They just want the timeline to "look" clean.)

(14) By Andy Bradford (andybradford) on 2023-04-18 13:50:33 in reply to 13 [link] [source]

> They just want the timeline to "look" clean.

Seems like a highly subjective valuation.

While  I  understand  the  motivation  behind  "hiding"  things  in  the
timeline, it seems like a counterproductive  thing to do, not to mention
potentially misleading and an anti-social activity.

Andy

(18) By Vadim Goncharov (nuclight) on 2023-06-30 01:04:03 in reply to 14 [link] [source]

As far as I understand, they in fact want just a "spoiler in a display" feature, that is, ability to collapse/expand parts of user interface (like parts of forum posts), which does not contradict fossil philosophy "everything recorded", just allows different forms of presentation for it (certainly this may save time in certain situations, not just only "cleaner look").

(22) By Ben (bvautier) on 2023-07-31 02:01:07 in reply to 18 [link] [source]

Exactly. Couldn't have said it better.

(17) By Alfred M. Szmidt (ams) on 2023-05-05 07:35:42 in reply to 1 [link] [source]

Sounds sorta like merge? You can hide the branch from the timeline, and it will look "simpler".

(23) By anonymous on 2023-08-23 17:39:04 in reply to 1 [link] [source]

You may consider Fossil private branches. Private branch stays local and also can be deleted with 'scrub --private' command. So make whatever changes in one private branch; merge (aka squash) them into another private branch; merge the desired state into the mainline; scrub the private branches.

"Commit early, commit often" in Fossil does make a noisy timeline, thus all that noise should generally stay in a branch. But it's still a noise, just for one developer... Git lets that developer deal with such noise by squashing, Fossil commonly insists on preserving the noise for posterity (which does make sense when published).

So if the timeline noise does bother you, then the private branch workflow may be a way to manage it.

Alternatively and depending on a project, it won't be cheating if Git is used at interim stages and committing into Fossil when "done". This does require some caution but both Git and Fossil could be made to coexist on the same source-tree.