HNHacker News
TopNewBestAskShowJobs

rbehrends

3,146 karma · joined December 27, 2011

submissionscomments
rbehrends··on Copy-on-write friendly Python garbage collection
Eh, incremental garbage collection is technology that has been around since the 1980s/1990s. For purely sequential systems (which is what we're talking about here), it is pretty easy to make GC soft real-time [1]. We are talking about maximum pause times well below just the network latency between the client and the web server.

Cache friendliness is a double-edged sword. There are plenty of use cases where a GC can be more cache-friendly than manual memory management, in particular if you have a generational GC with a bump allocator.

[1] What makes life (considerably) harder is when you have multiple threads sharing a heap, but that's not at issue here.

rbehrends··on Uber is officially a cab firm, says European court
Oh, it appears I misunderstood you. Yes, the federal government was willing to review the regulatory requirements, at least in principle. I was focusing on the last subclause of your sentence.
rbehrends··on Uber is officially a cab firm, says European court
> As result, there's many services like uber — but uber themselves is banned, because while the German government was willing to ignore the commercial drivers license requirement, they couldn't ignore the insurance requirements that Uber refused to fulfill.

The German government had nothing to do with it. The lawsuits were brought by Uber's competitors under §3a of the Unfair Competition Act [1], which prohibits violating laws in order to gain a competitive advantage. They have standing under §8 (3), item 1, of the same law. The courts had no choice but to rule in their favor; it was pretty much as open and shut as a civil case can be.

[1] https://www.gesetze-im-internet.de/englisch_uwg/englisch_uwg...

rbehrends··on Uber Is a Taxi Service, the E.C.J. Says, in Major Setback to Firm
It is largely a matter of standing. The Unfair Commercial Practices Directives leaves most of the details up to member states, but has a hard requirement that competitors have standing to bring legal action against unfair commercial practices [1]. Spain – where this case originated – also allows for consumer organizations to bring suit (but not individual consumers), but those will generally focus their resources on areas where competitors fail to act.

[1] Which was already a common approach in Europe before that directive was enacted.

rbehrends··on WhatsApp told to stop sharing user data with Facebook by French authorities
It isn't. If you look at the EU's information about it [1], you must be able to refuse processing of information that's not necessary for the functioning of the site. Virtually no site outside the EU's own ones does that. Ironically, there are some that have them even though the only cookies they use are ones that are necessary for the functioning of the site, and so are exempt.

Note that this isn't just about cookies. It's pretty much any information being sent from the user's computer or stored on the user's computer: "Member States shall ensure that the use of electronic communications networks to store information or to gain access to information stored in the terminal equipment of a subscriber or user is only allowed on condition that the subscriber or user concerned is provided with clear and comprehensive information in accordance with Directive 95/46/EC, inter alia about the purposes of the processing, and is offered the right to refuse such processing by the data controller."

As far as I can tell, this is mostly because member states capitulated before online advertising exchanges (it's a directive, not a regulation, so it is implemented by member states) and allowed them to work around the clear intent of the directive.

[1] http://ec.europa.eu/ipg/basics/legal/cookies/index_en.htm

rbehrends··on Overview of the Crystal language
This is a common complaint (not just for Crystal), but I would argue that most people do not actually need or want shared memory concurrency. They need either parallelism and asynchronicity, for which threads are just one possible implementation.

I'm assuming here that this is not just about multiple threads of control (which can also be achieved through having multiple processes), but having threads interact through shared memory rather than through message passing.

The problem is that with shared memory, there's a constant tension between performance and safety. Pretty much all safe approaches introduce either contention (through mutexes or other synchronization constructs) or considerable overhead (such as the O(log n) factor typical for purely functional data structures or creating duplicates to avoid contention). In order to write actual high performance code accessing shared memory, you will typically have to deal with inherently unsafe constructs.

In most cases, it's far easier to optimize message passing, perhaps throwing in some specialized shared memory constructs (such as Mnesia for Erlang) in order to make some common use cases less cumbersome. Note that with current memory hierarchies, the overhead of message passing compared to shared memory may be less than you think; any data that's actually used by more than one core has to pass through the L3 cache at a minimum.

I think that the reason why more people don't use message passing approaches is (1) that few languages actually support it well and (2) that message passing as a programming paradigm can be far more intimidating than shared memory, because causality is more difficult to reason about (though the ease of use of shared memory models can be deceptive and it's easy to run into difficult to diagnose correctness or performance problems). But technologically, message-passing is much easier to support. I've written highly parallel code in Python, Ruby, and OCaml, all of which have a GIL and cannot support shared memory parallelism. But they all have (near) universal serialization, so implementing basic message passing is trivial.

As an example, one of the main attractions of Go is, I think, that it provides a very accessible programming model for message passing (building on top of CSP). And we have PGAS approaches, which essentially simulate shared memory on top of a distributed architecture.

This is not to say that message passing is a panacea; there are plenty of use cases where it's a poor choice. For example, I'm currently working on a problem that is essentially about performing a reduction operation in parallel on multiple threads, where the operation is typically defined through a multi-GB data structure. Copying that data structure to every single thread is prohibitive, and keeping it in one thread would kill all the possible parallelism. But message passing is plenty good enough for 90% or so of all use cases.

Generally, I would phrase it so that the problems that shared memory and message passing have are duals of each other: shared memory is concerned with contention as a source of cost and complexity, message passing is concerned with communication as a source of cost and complexity. For the majority of problems, you can engineer solutions that fit either model (though they may look substantively different), so not using shared memory is not going to be a problem for those.

Asynchronicity is a different concern and does not actually require threads.

rbehrends··on High-Level Problems with Git and How to Fix Them
History is only "cleaner" because Git in its default configuration only throws the raw version graph at you and fails to visualize it in a readable fashion. With a properly structured visualization, such as Bazaar's hierarchical logs, merging is not just as clean, it actually carries more information.

In hierarchical logs, a merge commit stands for the series of commits that are being merged. You can then unfold such commits and view the series of commits as a nested list of commits (which may again contain merge commits).

Think of a merge commit as the equivalent of a procedure call where the procedure body is the series of commits being merged.

(Note that there are other ways to visualize version graphs with merge commits; this is just the simplest way to do it and could actually be easily added on top of Git.)

Rebasing has two problems. It discards the original version structure and it can create commits that never build or don't pass tests (because they never existed as such).

Frequent use of rebasing is almost always an indication that a VCS is lacking important functionality.

rbehrends··on High-Level Problems with Git and How to Fix Them
Sure, but the same things could happen to (say) a relational database under the same circumstances. There's nothing that you can do if (say) the hardware lies to you about having written bits to disk or reorders writes. I was citing the SQLite page for a reason. You can only work with the tools that the OS gives you.
rbehrends··on High-Level Problems with Git and How to Fix Them
> If Mercurial throws the old version away unconditionally, that would suck very much indeed.

Which is why it isn't done.

In core Mercurial, the old revisions are stored in a backup bundle in a separate backup directory. Note that bundles can transparently be used as read-only repositories, so you can view their logs as though they were still part of the parent repo, diff against them, pull from them, etc.

With the evolve extension, those revisions will simply be marked as obsolete, with obsolescence markers showing which revisions were replaced by which. The commits will be hidden, but are still part of the repository. If you ever want to get rid of the old revisions, you'd have to use (say) `hg strip -r 'exctinct()'`, which would store them as bundles as described above, or clone the repository and delete the old repository.

Plus, there are public, draft, and secret changesets. Public changesets are immutable and cannot be changed without user override.

Bazaar rebase will simply hide the old revisions; you can recover them with `bzr heads --all`. To permanently delete the revisions, you have to clone the repository and delete the old version (and all backups). And, of course, there's rarely a reason to use rebase in Bazaar.

> Hence: you either have a system that makes it much easier than Git to lose data, or you need garbage collection.

As I described above, neither. In every case, you need to go through several steps each requiring the user to affirmatively express their desire to delete data.

And as disk space is really cheap these days, hardly anyone ever actually deletes the data in practice, as there's no point to it.

> I don't know what Mercurial does, but somehow, the fact that this dilemma isn't obvious to you -- somebody who clearly seems to know a lot about Mercurial -- doesn't instill a lot of confidence in it.

I think your dilemma is largely an imaginary one, fretting over a resource (disk space) that is too plentiful to require micromanagement.

Keep in mind that most of the data in your repository will come from other people; there's only so much source code or text that a single person can write in a day. If you're generating massively large binary assets, a DVCS is probably the wrong tool, anyway, because of scaling concerns. This inherently limits the amount of "wasted" data that you can have in a repository to a percentage of the repository size.

rbehrends··on High-Level Problems with Git and How to Fix Them
I very much doubt that this is a Git issue, but suspect that it is instead a file system/OS/hardware issue. The problem is that operating systems generally offer only very limited guarantees about the atomicity of the bits actually being physically stored on a device (at least guarantees that can be used with reasonable efficiency).

It should not normally be a problem, but a power failure just at the wrong time seems the prime suspect to me; there's nothing in Git's logic that should normally allow for such a problem.

See, for example, the "Failure to sync" section of SQLite's page on "How to Corrupt an SQLite Database".

[1] https://www.sqlite.org/howtocorrupt.html#_failure_to_sync

rbehrends··on High-Level Problems with Git and How to Fix Them
> Question: How does Mercurial deal with garbage collection? After all, the desire for garbage collection is by far not unique to Git -- any version control system that has the equivalent of `commit --amend` and rebase should provide it.

Garbage collecion is an issue that is 100% unique to Git. No other VCS even thinks about throwing user data in the repository away without the user explicitly telling it to. Once you have a user telling you to throw the data away, it can do that. There is no need for GC; this is purely an artifact of Git's implementation. I'm honestly not sure why you think you'd even need a GC for `hg commit --amend` or `hg rebase` (or similar operations in other VCSes).

> As for not needing the extension, it seems to me that having "dangling" commits would be very difficult to use without some decent visualization of the dangling commits, such as what the blog post shows with `hg show`.

I'm not sure where you get the idea. This feature is, after all, not unique to Mercurial. It's Git that has the oddball semantics that no other VCS on earth has. Mercurial has been able to graphically show the graph for ages and the ability to just list open heads, too (`hg heads`). If you look at the code, the implementation of the show command is largely just a templated graphlog of a particular revset. For example, `hg wip` [1] (for "work in progress") has been doing something similar just using revsets and templates from core Mercurial.

> The point of the purely functional data structures isn't to achieve atomicity (although potentially being a bit more robust to power loss etc. is certainly a nice side effect), it's a way of thinking about version control. I've never heard functional programmers use atomicity as the main argument for immutable data structures, either...

This is because functional languages do not have to worry about their state being destroyed by the user hitting Control-C or a power outage. This will simply terminate the program, whereas for Git it will interrupt a transaction in progress.

> The whole point of Git's design is that it chose a robust and crystal clear way of thinking about distributed versioning as its underlying model of what version control is, and then simply provided tools for manipulating that DAG.

This is what other version control systems do, too, without relying on purely functional data structures. The fact that the data structures are purely functional is, after all, not a property that is visible to the user other than through the side effects of garbage collection.

[1] http://jordi.inversethought.com/blog/customising-mercurial-l...

rbehrends··on High-Level Problems with Git and How to Fix Them
> As a convinced Git user, this is the one aspect of the article that I found genuinely interesting. I mean, you can commit anywhere in Git, too (and I occasionally do for some advanced use cases), but it tends to not be well supported by Git.

"Not well supported" in this context means that you can lose data (as commits that are not reachable from a ref can be garbage collected). Mind you, you have to ignore warnings/errors to get there, but without ignoring them, you also won't be able to actually have anonymous branches.

> (And, I might add, probably not by Hg either if it requires enabling an extension...)

It does not require an extension. The `show` extension that Greg talks about is a smart log display, which shows a contextual log for your current work. Anonymous branches have been part of Mercurial from the beginning, long before the `show` extension existed.

For what it's worth, it's not so much that Mercurial supports it, but that Git doesn't. Git is rather unique in its setup in this regard. No other VCS that I know of uses garbage collection and in particular branches to prevent revisions from being garbage collected. (Some may require you to name branches for other reasons, but not so you don't lose commits.)

It's probably also worth noting that we have an implementation detail leaking into user space here. Git uses what is essentially purely functional data structures underneath to achieve atomicity in the absence of an actual database engine [1]; a different implementation, such as on top of SQLite, could avoid this.

[1] Atomicity here is not about locking, but about a transaction being interrupted by the user or an external event, such as a shutdown.

rbehrends··on High-Level Problems with Git and How to Fix Them
Checkpointing is generally different from commits in more than just not having a commit message (ex: should one allow checkpoints to be pushed? should they be suppressed in the log by default? should they be part of the normal revision graph or a separate hierarchy?).

Yes, you can emulate them because Git is a general versioned, hierarchical key value store. But by the same token, you don't really need Git, you could just store each new version in a separate directory. A version control system is about more than providing raw access to a storage engine; it needs to support appropriate high-level operation and integration with the development process.

The problem here is the Procrustean way in which people try to force everything to fit Git's model rather than to think beyond its limitations (many of which are shared by other VCSes, I don't want to single Git out here) and to address these problems at the user experience level (especially when we're talking about normal users, such as writers and artists, who may not have a deep understanding of the technical details).

rbehrends··on High-Level Problems with Git and How to Fix Them
The thing is that you don't need a staging area for that. The staging area is an unnecessarily complicated way to address this particular problem.

More importantly, the article is about the staging area being there by default.

rbehrends··on High-Level Problems with Git and How to Fix Them
1. The problem with `hg strip` is how it is (and has to be) implemented, because of the revlog format. Basically, revlogs store changesets in chronological order. Stripping a branch means first saving all commits that are not being stripped (but occur chronologically after the first stripped commit) to a bundle, then truncating the revlog before the first stripped commit, then restoring the commits from the bundle. This can already be an expensive operation locally, but it's even more of a problem on a server shared by multiple users who may have been pushing their own commits.

2. The general recommendation would be to use `hg prune` (part of the evolve extension) instead. Pruning commits will just hide the pruned commits and pushing will then send the obsolescence markers for those commits to the server, hiding them there, too. This is an append-only operation, so it's cheap and works well even with multiple users.

3. In general, though, it is a problem that Git and Mercurial treat remote branches/repositories as second class citizens. This is something that Bazaar got right: Bazaar abstracts over the storage, so (except where limited by network performance) you can do pretty much anything on a remote branch/repo that you can do locally.

rbehrends··on High-Level Problems with Git and How to Fix Them
> So because your ass is just lazy another guy a few months down the road has to suffer (in this case decipher what it is you wanted to do with your changes)?

The problem that Greg is referring to here is that `git commit` (as for many other version control systems) overloads two distinct operations: checkpointing your work and creating a new revision. Forcing you to provide messages for the former use case can even be counterproductive [1].

Contrast this with, say, Smalltalk's ENVY: saving a method automatically versioned the method (without the need of providing any other metadata), but you could also separately create system snapshots as named versions.

Common Git usage is to squash checkpoint commits, anyway, with individual commit messages often disappearing in the process.

[1] https://xkcd.com/1296/

rbehrends··on List Comprehension in Swift
> Then tell me, what was I saying?

Let me rephrase then: what you seemed to be saying.

> ...you should get off your high horse. Your juggling with math terminology earlier is not impressive.

I'm not sure where you got that impression. I learned about Cartesian products in high school. Everything else I was talking about is at most first year computer science stuff.

I was simply trying to be precise, using the most basic terminology I could think of.

rbehrends··on List Comprehension in Swift
So, your problem is that Swift's syntax in general isn't to your tastes, not the list comprehension proposal as such? Fair enough, but that's not what you were saying.
rbehrends··on List Comprehension in Swift
This does not require a different syntax. You'd just construct a lazy list instead of an array.

Neither is an improvement over the other. Sometimes it's more important to have the results up front fast, sometimes generating them on demand is better.

rbehrends··on List Comprehension in Swift
A list comprehension is basically a cartesian product with a filter and map applied to it.

What we have here is an array constructor, which takes one or more ranges as its arguments. The named `where:` argument provides the filter closure and the final argument is the map, using standard trailing closure syntax.

In fact, other than the missing implementation of the constructor, this would be perfectly legal Swift code today.

I'm not sure what's there to be confused about. `x..<y` describes an open range (including x, excluding y). `{x, y in ... }` is normal Swift closure syntax, where `x` and `y` are the arguments; you can also use positional arguments as in `{ ($0, $1) }` instead.

If the final argument of a function is a closure, it can be (as in Ruby) be written after the closing parenthesis of the function call.

rbehrends··on Using Rust in Mercurial
As the plan seems to be to embed Python in Rust, rather than just writing extension modules in Rust, this shouldn't be as much of a concern.

The tricky part with two runtimes is (1) the system initialization and (2) the GC (if any) being able to control the initial stack frame. If you're just writing extension modules, either problem can be a challenge.

But the first problem goes away if you embed Python, as Python initialization is trivial for the host language when embedding. So does the second problem, as the host language is now in control of the initial stack frame.

The fact that a language is garbage-collected should not matter much; Python uses reference counting as its primary memory management mechanism, with a generational trial deletion approach for cycle colletion (which does not require root scanning). This approach can generally coexist well with a tracing garbage collector (though some challenges remain, but those can be designed around).

That said, Go specifically may have problems (I'm speculating here) due its green threads not being happy if Python does any blocking operations, and it's probably not safe to operate on Python objects concurrently in multiple threads. But that wouldn't necessarily be a problem for other languages.

rbehrends··on Integrating “safe” languages into OpenBSD?
Sorry, I didn't mean to imply that Rust did. For me, it's primarily a problem with some JVM-related tools.
rbehrends··on Integrating “safe” languages into OpenBSD?
But a C compiler is part of basically every Unix system. That matters. For example, one of the perks of my job is that I do occasionally get to play with real nice hardware (such as a Cray with a couple thousand nodes at one point). The downside of that is that the owners won't give me root access and because there are lots of other people working with it, stability of the system is more important than getting the newest software installed. Oh, and this also sometimes means restricted internet access.

So, bootstrapping matters to me, and the bootstrapping story for anything involving LLVM isn't very good (I'm actually having some problems with a language other than Rust in this regard).

Interpreters you can generally build fairly easily from source (Lua, Ruby, Python, Tcl, for example). But when it comes to compilers, bootstrapping is generally a much bigger obstacle. There are a few notable exceptions that only require a C compiler, no internet access during their build, and build in an acceptably short time:

* OCaml bootstraps from C via a bytecode interpreter, which is then used to build the native compiler. On my laptop (using four cores), I can actually do that in under a minute (two minutes if I want flambda).

* Nim compiles to C and hence builds in half a minute on the same machine from C sources.

* LuaJIT builds in a few seconds, assuming a dynamically typed language with a JIT compiler is sufficient for you.

rbehrends··on Fossil – Next Generation
> The way objects and branch references was set up by git-new-workdir was completely safe for git-gc not to lose other workdir's data.

The simplest example is when the only live reference to a commit is a detached HEAD. Because HEAD is duplicated per workdir, other workdirs do not know about HEAD, and if it doesn't refer to an actual ref, it's subject to garbage collection.

This isn't much of a risk if there's constant ongoing work (because then the GC grace period and the reflog will typically save you), but it can be a problem if you're dealing with a branch that sees only intermittent work.

Even without that, it's pretty easy to destroy graph ancestry (which is not actual repository content, but still fairly important metadata). Example:

  set -e
  git init g
  cd g
  echo 1 >foo
  git add foo
  git commit -m test
  cd ..
  sh git-new-workdir g g2
  cd g2
  git checkout -b foo
  echo 2 >foo
  git commit -a -m foo
  git branch -d master
  cd ../g
  echo 3 >foo
  git commit -a -m master
  git log --graph --all
The above script will disconnect one branch from the rest of the repository.

The underlying problem is that there are some important invariants that Git needs to maintain to ensure repository integrity in the presence of mutable history, and if part of the information is held in a place that it does not know about, it may not be able to maintain those invariants.

There's a reason why `git worktree` goes to a lot more effort to prevent such scenarios by registering worktrees with the repo and why `git worktree lock` is still needed.

rbehrends··on Fossil – Next Generation
I am familiar with git-new-workdir. However, git-new-workdir wasn't safe and could lose you data.
rbehrends··on Fossil – Next Generation
That's largely a UI issue. Look at Bazaar's hierarchical logs, for example [1]. I call it largely a UI issue because:

1. You can run `bzr log` on a git repository (with the bzr-git plugin). Hence it's a UI issue.

2. That operation still takes O(history size) time, because git repositories lack the necessary metadata to make the operation fast (hence: "largely" a UI issue).

[1] https://bzrinit.com/05.html#hide

rbehrends··on Fossil – Next Generation
I disagree with the "progress" part. Git is still the 2005 state of the art, and in parts not even that (it took until 2015 to even support something as basic as multiple checkouts of the same repository via `git worktree`).

Git is generally not very suitable for new features, because the repository format is very restrictive and has no room for adding new metadata (it's not impossible, but it's always going to be a hack).

Most actual progress in version control has happened in systems other than Git, both open source and commercial (what Git has gotten is a lot more polish and ecosystem integration).

My general criticism of Git isn't that it's bad, but that it's the lowest common denominator of modern version control and has resulted in technology being locked in to yesterday's state. At the same time, it's awfully complicated for how relatively basic its feature set is. It is not a bad tool, but it's not a particularly attractive one, either.

Also:

> It took a while to get there, we had to suffer through decades of crappy source control systems until we eventually stumbled upon a good one.

Umm, what? Git wasn't the first attempt at improving upon CVS or SVN, and was arguably in some aspects worse than some of its predecessors (it often had higher implementation quality, but in terms of features it was also often a step backwards). I think lots of people are not familiar with PRCS (including the specced-out, but never implemented PRCS2), GNU arch, Monotone, or Darcs, not counting commercial or more esoteric ones.

rbehrends··on Fossil – Next Generation
Things I like about Fossil:

* Single file repository with proper transactional semantics (it's an SQLite DB) rather than a "pile of files" implementation. I can trivially and safely copy the repository, send it as a mail to a new collaborator, etc.

* Fossil is a single executable. Unlike Git or Mercurial, that makes installation trivial.

* The whole system is remarkably clean, simple, and lacking in cruft. This also makes it easy to explain and get people started.

* Fossil has a well thought-out system for tags and named branches; they are persistent, but can be renamed. This is different from Git, where history tends to be largely a soup of anonymous objects, and Mercurial, where it is impossible to rename branches.

* It comes with a built-in wiki and distributed ticketing system. If you want to self-host rather than rely on GitHub/Bitbucket, that's already a very useful feature.

* `fossil undo` is very useful, even though it supports only one level of undo.

* The ability to store and share unversioned files is often very convenient.

Things I don't like about Fossil:

* The console interface is sometimes too simplistic for some commands (`fossil timeline` is one of the biggest offenders); it errs in the opposite direction from Git and relies on a GUI too much for basic operation (though I'd still prefer it over Git, but not necessarily other VCSes). That said, you can script it fairly adequately either with the JSON interface or even through direct SQL queries if you don't mind investing the time.

* The workflow for external contributions for contributors without commit access is more cumbersome than necessary; in principle, `fossil bundle` serves that need, but it lacks some features (you can't bundle non-commit artifacts, for example).

* Commit messages all get squished into a single line of output. You could argue that wiki pages or technotes are the proper way to document more complex information (so you don't have to sift through commit messages), but it's difficult to relate them to a specific commit and they aren't supported by `fossil bundle`, as mentioned above. There's some unfinished work on attaching remarks to commits [1], but this work seems dormant.

* Scalability is even more of a problem than with other distributed version control systems if you're dealing with really big repositories (not that those other systems necessarily cover themselves in glory, either), though the situation has improved in recent years.

[1] https://www.fossil-scm.org/index.html/timeline?r=remarks-on-...

rbehrends··on Fossil – Next Generation
> Also, they claim this provides assistance in meeting regulatory standards, but never saw much more than assertions on that.

This is probably in reference to this old message by Richard D. Hipp [1]:

"Fossil, in contrast, is designed to remember everything. Fossil was specifically designed to support the DO-178B inspired development process used by SQLite, with few developers and a complete and immutable audit trail for all inputs."

DO-178B, now superseded by DO-178C [2], was an FAA standard used for the approval of commercial software-based aerospace systems.

[1] https://www.mail-archive.com/fossil-users@lists.fossil-scm.o...

[2] https://en.wikipedia.org/wiki/DO-178C

rbehrends··on Fossil – Next Generation
Only with a fair amount of pain (basically, `fossil merge --cherrypick`, then `fossil purge` for the original branch).

That's because mutable history is not part of the normal workflow that's being encouraged by fossil. You will see that "wrong" branches are simply tagged as "mistake" in the Fossil or SQLite repositories [1]. Branches are merged rather than rebased and the web UI is built around the assumption of having branches as distinct, named entities, rather than having them coalesced into the mainline. Fossil also uses autosync by default, where commits are automatically pushed to the master repository, which further discourages changing history.

If you simply want a local checkpointing mechanism (where you snapshot revisions at intervals with the intent to later combine them into a single meaningful commit), that's what private branches or `fossil stash snapshot` are for. Commit metadata, such as commit messages, can be changed later, but the edits leave an audit trail.

A major reason here is specifically that changes are supposed to always have an audit trail.

[1] https://www.sqlite.org/cgi/src/timeline?n=100&r=mistake

← PreviousPage 5 of 31Next →