blob: ab896b86770b11528a69a063fc79e6e828bcf325 [file] [log] [blame]
*** Branches and Tags -- Brainstorm ***
or
M7: The Final Frontier
First, let's define what it means to create branches/tags in a
repository. Simply put, the repository creates a new dirent which is
just an "fs_link" to an existent directory node-rev-id. It's the same
process that happens when we build transactions.
That reminds me of an important point; sometimes the user wants to
make a "true" copy of something (svn_fs_copy), and sometimes the user
just wants to make a "cheap" copy of something (svn_fs_link).
[svn_fs_copy]
* Use case: the user has a file or dir that she wants to duplicate.
Instead, the user would type 'svn cp foo bar', and bar would
automatically be scheduled for addition (and have other ancestry
info recorded).
* do a system copy of foo to bar, including props and text-base
* schedule 'foo' for deletion in its original parent
* schedule 'bar' for addition -- with ancestry -- in a new parent
The commit would supply "copyfrom" args to the editor, which would
then run 'svn_fs_copy'. The new node-rev-id for bar would then
contain special copy-info in its skel.
This works as of M6, for files at least.
[svn_fs_rename]
* Use case: the user wants to rename/move a file or dir.
In M6, this is 'svn mv src dst' is just a wrapper (literally!)
around 'svn cp src dst; svn rm src'.
Here's the problem: why on earth wouldn't the commit-process just
run 'svn_fs_copy' when it sees the editor's "copyfrom" args on bar?
We want 'svn_fs_rename' to run instead, which means that the editor
somehow needs to realize that the deletion and addition are
connected.
Cmpilato sez: svn_fs_rename exists only to "fill out" the
filesystem API. But we never expect an svn client to call it; our
editor model has already defined that a 'move' is an 'add with
history and a delete." So we're fine.
[svn_fs_link]
* Use case: the user wants to create a tag or branch.
Because the user may not have a large-enough tree checked out, I
think it's best that the 'svn branch' and 'svn tag' subcommands
take two URLs as "source" and "dest" arguments. This even allows
someone to make tags/branches without a working copy.
(Eventually, I suppose, we could loosen the restriction and allow
people to refer to paths in their working copy. As with 'svn log',
the wc paths are just converted to repository URLs anyway.)
So a user might type
% svn tag http://svn.collab.net/repos/svn/trunk \
http://svn.collab.net/repos/svn/branches/one
And this command would cause a simple 'svn_fs_link' in the
repository. (### RA mechanism for this? )
To remove the tag, a user could check out the repository's root and
just delete the tags/m5 directory and commit. Or maybe a different
subcommand could make this an easier task -- one which wouldn't
require a big working copy.
[ Nothing could be easier:
svn remove http://svn.collab.net/repos/svn/branches/one
--xbc ]
[dir_delta updates]
* Use case: the user wants to move their existing working copy onto a
tag or branch.
I guess this is just a plain old update, except that dir_delta
isn't going to be called with identical path arguments (like it
usually is.)
dir_delta notices a relationship between the two paths, and
therefore won't do anything stupid like delete and re-checkout your
whole working copy. Instead, it sends patches as needed, based on
relationships between node-rev-ids. (At least, according to
cmpilato, dir_deltas already *should* behave this way.)
As usual with updates, local mods will be preserved, merged, or
come into conflict. If the user really wanted a perfectly clean
branch-move, they shouldn't have local mods lying around... or they
should just checkout the branch directly!
THE REAL WORK: instead of running log_do_committed during the update,
the editor should be running a *new* log command that tweaks the
'ancestry' URLs in the entries files. The entries files need to
point to the new branch URL. [Ben sez: this same URL-tweaking
functionality is needed when running 'svn cp dir1 dir2'.]
[MERGING]
Assume that our working copy corresponds to /branches/one in the
repository. /branches/one started out as a cheap copy of /trunk
some weeks ago, but both directories have received commits since
then.
Now we want to absorb any new changes from /trunk into our working
copy branch.
Here's what needs to happen:
* somebody calls dir_delta with two special arguments. The first
argument is the common ancestor of /branches/one and /trunk; in
other words: "/branches/one in the revision where it first came
into existence". The second argument is "/trunk in the head
revision."
* Now the changes start coming down to the working copy.
Unfortunately, the working copy can't just use its vanilla
update editor to apply them. Why not? Because dir_deltas
thinks that the working copy is different than it really is --
it thinks the working copy looks like the very origin of
/branches/one!
Therefore, one of two things need to happen:
* dir_delta is told to send fulltext (because any svndiff
patches *wouldn't* correctly apply to pristine files!) The
client then does a diff between the fulltext and the
pristine file, and applies that patch to the working file.
The fulltext is then deleted, and the pristine copy is NOT
touched! (Otherwise our next commit would be screwy!)
* dir_delta sends a flexible, *contextual* diffs against the
head of /trunk. We then apply these diffs directly to the
working files (again, leaving the pristine files
untouched.)
[GENETIC MERGING]
In theory, every time we do a merge, we should *store* the second
argument to dir_delta in the working copy somewhere. In other
words, we remember exactly the "tip" of trunk from which changes
were merged.
Now, the next time you merge, this "tip" becomes the *first*
argument (base) to dir_delta. This prevents you from re-merging
patches you already have, and solves a major CVS annoyance.
Really, I'm not sure we ever need to track each merged changeset
one by one. That might be unnecessary here.