Bazaar

Bazaar

 




Wiki Tools

  • Find Page
  • Recent Changes
  • Page History
  • Attachments

This specification has been implemented and will be released in bzr 0.15. This page remains for historical interest. Some of the refinements which are not yet implemented have been recorded as bugs.

Tagging in Bazaar

Introduction

Tags provide meaningful user-assigned names for revisions. Tags are typically used to mark release versions, or other points of interest.

The common case is that each tag name is written only once, when the tag is "placed". It's also possible to delete or replace tags, but this will typically only be done if a tag was incorrectly placed, and typically requires a --force option. (If a name needs to point to different code as development proceeds it may be more appropriate to use a branch.)

Tag names are non-whitespace Unicode strings starting with a letter. It's recommended that the tag name start with a project identifier: for example, bzr-release-0.9 or hp-ijs-1.2.12.

(Rationale: other systems disallow whitespace in tag names, and it makes representation in text files or on command lines easier.)
Behaviour of tags

Tags are ''versioned'': it's possible to see when a tag was placed, and by whom. If a tag is later moved or deleted, that's also recorded.

Tags are defined by revisions; the current tags for a branch are those of the branch's tip revision.

Tags ''propagate'' across push, pull, and merge. When a branch is pushed or pulled to a mirror, the tags go across too. When branches are merged, their tags are reconciled.

It's possible there will be a conflict in tag definitions during a merge. This should be rare, as we assume each tag will only be placed once. If there is a conflict, it can be handled through the usual cycle: the merge command completes, the working tree is put in a conflicted state, the user inspects and resolves the conflicts, and then commits a new revision resolving the conflict.

The normal case is that tags are placed at the time the revision is committed.

Unless the --force option is given, it is an error to try to place a tag that already exists.

User stories

A revision can be tagged as it's created
bzr commit --tag TAG_NAME ...

This adds the new revision id to the tag dictionary in the working directory before committing it.

The option can be repeated to add multiple tags pointing to the same revision.

If the commit is interrupted, the tag name is removed (i.e. changes to the tag dictionary are rolled back.)

This is the recommended way to place tags, because the tag will be present in the revision it describes.

An existing revision can be tagged
bzr tag -r 123 TAG_NAME

This commits a new revision adding a tag. If the location contains a working directory, it is rebased onto the newly-committed revision.

The location of a branch or working directory can be given to create a tag in that branch:

bzr tag -r 123 TAG_NAME -d LOCATION
Diff against a tagged revision

Compare working directory to an existing tag in this branch:

bzr diff -r tag:TAG_NAME
Make a new branch from an existing tag
bzr branch -r tag:TAG_NAME SOURCE NEW_BRANCH

This looks up the tag within the source's working tree or tip revision.

Look up up the tag name in the branch's tip revision. Make a new branch from this revision.

Within the new branch and its working tree, the tag dictionary will be the one from the previously recorded revision.

If that revisions tag was placed with commit --tag, then the definition of the tag will be present in the revision.

If the tag was created or changed after the revision was written out, then the tag's own definition won't be visible in the new branch. In either case, no later tags will be visible within that space. The new tag definitions can be seen by looking at the later branch.

Revert a working directory to the tree of an existing tag

The tag name is mapped to a revision id, and the tree is reverted to that revision. This implies reverting the working tree's tag revisions.

Within a branch from older tag, compare to newer tag

A branch made from an older tag contains the definition of tags from that older time, as do working directories created from that branch.

To compare to a newer tag it is necessary to point to a branch that contains the new branch. So it would work to do:

bzr branch -r bzr-0.9 ./bzr.dev ./bzr-0.9-fixes
cd bzr-0.9-fixes
bzr diff -r ..bzr-0.10 . ../bzr.dev

The tag bzr-0.10 is looked up within the relevant context, which is ../bzr.dev.

Remove an existing tag
bzr tag --delete TAG_NAME
bzr tag -d TAG_NAME
Move an incorrectly-placed tag
bzr tag --force -r 1234 TAG_NAME
Resolve conflicting tag definitions

The same name is assigned to different revisions in two branches. This is allowed, even if the two branches are stored in the same repository. When the two branches are merged, the user doing the merge must choose one definition (or remove the tag).

In the initial implementation users might be required to just edit the file directly to make a choice:

vi .bzr/checkout/tags
See a list of all tags in a branch

This should be available from the command line, but also probably from within the gui or a web interface.

bzr lstags

Logs are displayed with tag names taken from the current dictionary next to the relevant revisions:

bzr log
Show the history of a tag

It's possible to find out when and by whom a tag was initially created, and the history of any changes.

Possible interface:

bzr log --tag TAGNAME

123
2006-04-01
user@domain
   create tag TAGNAME pointing to 123
Copy tags between branch mirrors

A tag is created in a branch, which is then pushed into a mirror. The tag definition should then be visible in the mirror. If someone else pulls from there into their own mirror, the same tag definition carries across.

Example:

cd a
bzr commit --tag FOO
bzr push ../b
cd ../b
bzr log -r tag:FOO
Merge tags between branches

A user is maintaining a long-running feature branch. From time to time they merge in the project's mainline branch. After a merge, any new tags which were added in the mainline (or branches merged into it) should also be visible in the merge result.

Set per-user tags

A user's doing some work and often comparing it to a particular previous revision. They'd like to assign a name to that revision, but it won't be meaningful to other people, or important beyond the scope of this particular work session.

They might like to set a name that only exists within the working directory, or in their ~/.bazaar directory.

This case seems to require a different mechanism and the case seems a bit contrived; it can be deferred for now.
Compare tag definitions in two branches

Perhaps run bzr tag --show in both branches, and compare the output.

Update tags from another branch

In a fix branch it might be desired to update the tag definitions from the main branch without bringing across any changes.

A manual way of doing this would be bzr tag --show | bzr tag --set -

Or alternatively bzr merge --tags-only FROM_BRANCH

Import from Subversion, preserving tags

Tags are made in Subversion through an idiom of making a copy of the whole tree to a /tags/ directory in the repository. The importer can look in the tags directory for copies from the branch it's importing. When one is detected, it should create a bzr tags at that point.

Import from CVS, preserving tags

This is difficult because tags can be set on individual files in ways that are not representable as whole-tree tags.

We might be able to steal the logic for this from Subversions' cvs2svn utility. -- JelmerVernooij

Implementation

Tag dictionary

"Tag dictionaries" are a dictionary of tag_name -> revision_id.

Each revision contains tag dictionaries current for that revision. By default, and in the empty revision, this set is empty. This is written when the revision is created and (as for other revision metadata) is not semantically changed after it's first written. These are the tags current for that revision.

A working tree also contains a tag dictionary. Unlike the stored revisions, this can be changed by user operations.

Tags are "looked up" to map a name to a revision_id. This lookup is done in the context of a working directory or a branch. If the context has a working tree, the tag is looked up in the working tree's dictionary. Otherwise, the tag is looked up in the dictionary of the last revision of the corresponding branch.

A tag dictionary can be represented by a UTF-8 text file containing lines of:

tag TAG_NAME REVISION_ID

There is a literal word tag at the start. Lines starting with # are ignored. Everything else is reserved for future use.

This representation can be presented to the user when displaying tags or editing the entire list.

Working tree

Working trees contain their current tag dictionary. This is updated from branches when the working tree is built, updated, merged, etc.

The tag dictionary can be held in a file either at the top level (.bzrtags) or inside the control directory (.bzr/checkout/tags). In either case, the working tags file should not be added to the inventory.

XXX: Should this require a new working directory format? It seems that it will, otherwise we won't know whether the tags file is up to date or not. There may not be any mechanism yet to upgrade working trees. Perhaps there should be done through bzr upgrade, or perhaps it can be automatically triggered through operations such as upgrade.

Updating or reverting the tree should update the tags. (This behaviour should fall out through having the tree delta describe tag changes, and updates apply them.)

Constraint on tags

The tags in a revision's dictionary should all point to ancestors of that revision, or the revision itself. Tags in a working directory must point to one of the basis revisions, or one of their parents.

This forbids several situations:

  • tags cannot point to future revisions
  • tags cannot point to unrelated revisions
  • tags cannot point to nonexistent revisions

This means that a normal fetch of a revision and its ancestors will implicitly ensure that all tags in that revision can be resolved. (It's possible that the tag revisions may be ghosts or past a history horizon, as for other revisions.)

This constraint should be enforced when a -r option is given to bzr tag.

Conflicts

The conflict system should be able to record that there is a tags conflict.

Conflict indicators should be written into the working tree's tag file.

Tags file in working tree

The working tree's tags are stored in .bzr/checkout/tags.

Commit

Commit needs to store a new version of the tags dictionary, with the revision id of the newly committed version.

Historic revisions

Historic revisions should be able to report the tags relevant to them.

Fetch

Fetch (called by push, pull, merge) should move across tag definitions corresponding to the copied revisions.

Tree comparison

Changes in tags should be reported by tree comparison. Changes can be just addition or deletion.

Revision specifiers

Tag lookup is initiated through the revision spec mechanism currently used to look up revisions by revno, revision_id, date, etc. Tags create a new tag: revision namespace.

We have the option in the future to use tag names as a catchall to interpret revision specs that don't have a namespace prefix and don't look like a revision number.
Bundles

Bundles must serialize the changes in tags described by tree comparisons.

Testaments

Testaments should cover the tags in a revision. This requires a new testament version.

Open questions

Rather than all these options, perhaps we should have 'bzr rmtag', 'bzr lstags', etc?

I think the command to list tags should be "bzr tags" (and not "bzr lstags") to be consistent with other working directory related commands, such as "bzr unknowns". --JohanRydberg

Alternative mechanisms

There are four main possible approaches:

[type 0] Tags are not versioned (ie once they are changed, you can't see the previous value.)

This is OK for some uses, but is insufficient because it loses historical data, and because tags can't propagate into mirrors without a special mechanism.

[type 1] Tags are versioned in the same timeline as revisions. (Changing tags requires creating a new revision, possibly implicitly.)

[type 2] Tags are versioned in a different timeline from revisions.

[type 3] There is no tagging mechanism; we just suggest to use branches instead.

This is like svn's model, and has the advantage of not introducing a new primitive. However, this design in svn is widely disliked, because users do seem to think of tags and branches differently. It hides informtion about when the tag was created or modified: you only see the revisions which were pulled or committed into the branch.

This can perform well if the branches are stored in a repository, but if they're standalone creating a tag will be expensive.

Dot file versus special mechanism

Rather than handling this as a special mechanism, why not simply put it in a dot file in the root directory?

We can add a file-specific merge helper to do special reconciliation of tags.

Pros:

  • Smaller changes: no need to modify the working tree, bundles, testaments, etc.
  • No need for a new working tree or repository format.
  • This is consistent with how ignore patterns are stored in /.bzrignore. Both are mutable within the working directory and then stored into history.
  • Merges and conflicts are handled in the usual way. Conflicts don't need to be specially represented.
  • Less need for explicit code to list or remove tags. Commands can be added in the future but users can look directly at the future at first.
  • Preserves the idea that users should not touch anything under .bzr/ even for advanced uses.

Cons:

  • Keeping tags out of the inventory may make it easier to translate them semantically to and from other systems. There's no question that the working-directory file should be written into the destination system.
  • The file is treated as a text file; there's no straightforward way for format upgrades to rewrite the file.
  • The tags file intrudes on the user's working directory. Again, this is no worse than the ignore file, and in general files matching .bzr* should be considered reserved.
  • To get the tags for a branch, we need to indirect through its inventory to get the right version of the tags file. This may be somewhat slower or more complex. On the other hand, for any high level operation we only need to look at one tag dictionary so the cost should not be too high. General efforts to speed up access to files of a particular name in a particular tree will be faster.
  • Having started recording that it exists as a plain file we probably have to preserve that idea in future.
  • Although this is consistent with .bzrignore, the current representation of ignore patterns within that file is not completely satisfactory.