From: Gabriel Filion Date: Fri, 26 Nov 2010 11:00:35 +0000 (-0500) Subject: add a tag command X-Git-Tag: bup-0.21-rc1~14 X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?p=bup.git;a=commitdiff_plain;h=9665d202d372b499802e759f29c97f3861eb247c add a tag command Currently implemented: list all tags, add a tag on a specific commit or head, delete a known tag. Also, make vfs expose a new directory called '/.tag' which contains a link for each tag to its associated commit directory situated in '/.commit'. Finally, make tags appear as symlinks on branch directories on which the tagged commit is visible. Signed-off-by: Gabriel Filion --- diff --git a/Documentation/bup-tag.md b/Documentation/bup-tag.md new file mode 100644 index 0000000..77b04ff --- /dev/null +++ b/Documentation/bup-tag.md @@ -0,0 +1,64 @@ +% bup-tag(1) Bup %BUP_VERSION% +% Gabriel Filion +% %BUP_DATE% + +# NAME + +bup-tag - tag a commit in the bup repository + +# SYNOPSIS + +bup tag + +bup tag \ \ + +bup tag -d \ + +# DESCRIPTION + +`bup tag` lists, creates or deletes a tag in the bup repository. + +A tag is an easy way to retreive a specific commit. It can be used to mark a +specific backup for easier retrieval later. + +When called without any arguments, the command lists all tags that can +be found in the repository. When called with a tag name and a commit ID +or ref name, it creates a new tag with the given name, if it doesn't +already exist, that points to the commit given in the second argument. When +called with '-d' and a tag name, it removes the given tag, if it exists. + +bup exposes the contents of backups with current tags, via any command that +lists or shows backups. They can be found under the /.tag directory. For +example, the 'ftp' command will show the tag named 'tag1' under /.tag/tag1. + +Tags are also exposed under the branches from which they can be reached. For +example, if you create a tag named 'important' under branch 'computerX', you +will also be able to retrieve the contents of the backup that was tagged under +/computerX/important. This is done as a convenience, and should the branch +'computerX' be deleted, the contents of the tagged backup will be available +through /.tag/important as long as the tag is not deleted. + +# OPTIONS + +-d, --delete +: delete a tag + +# EXAMPLE + + $ bup tag new-puppet-version hostx-backup + + $ bup tag + new-puppet-version + + $ bup ftp "ls /.tag/new-puppet-version" + files.. + + $ bup tag -d new-puppet-version + +# SEE ALSO + +`bup-save`(1), `bup-split`(1), `bup-ftp`(1), `bup-fuse`(1), `bup-web`(1) + +# BUP + +Part of the `bup`(1) suite. diff --git a/cmd/tag-cmd.py b/cmd/tag-cmd.py new file mode 100755 index 0000000..760dce7 --- /dev/null +++ b/cmd/tag-cmd.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +"""Tag a commit in the bup repository. +Creating a tag on a commit can be used for avoiding automatic cleanup from +removing this commit due to old age. +""" +import sys +import os + +from bup import git, options +from bup.helpers import * + + +handle_ctrl_c() + +optspec = """ +bup tag +bup tag +bup tag -d +-- +d,delete= Delete a tag +""" + +o = options.Options('bup tag', optspec) +(opt, flags, extra) = o.parse(sys.argv[1:]) + +git.check_repo_or_die() + +if opt.delete: + tag_file = git.repo('refs/tags/%s' % opt.delete) + debug1("tag file: %s\n" % tag_file) + if not os.path.exists(tag_file): + log("bup: error: tag '%s' not found." % opt.delete) + sys.exit(1) + + try: + os.unlink(tag_file) + except OSError, e: + log("bup: error: unable to delete tag: %s" % e) + sys.exit(1) + + sys.exit(0) + +tags = [] +for (t, dummy) in git.list_refs(): + if t.startswith('refs/tags/'): + tags.append(t[10:]) + +if not extra: + for t in tags: + log("%s\n" % t) + sys.exit(0) +elif len(extra) != 2: + log('bup: error: no ref or hash given.') + sys.exit(1) + +tag_name = extra[0] +commit = extra[1] +debug1("from args: tag name = %s; commit = %s\n" % (tag_name, commit)) + +if tag_name in tags: + log("bup: error: tag '%s' already exists" % tag_name) + sys.exit(1) + +hash = git.rev_parse(commit) +if not hash: + log("bup: error: commit %s not found." % commit) + sys.exit(2) + +pL = git.PackIdxList(git.repo('objects/pack')) +if not pL.exists(hash): + log("bup: error: commit %s not found." % commit) + sys.exit(2) + +tag_file = git.repo('refs/tags/%s' % tag_name) +try: + tag = file(tag_file, 'w') +except OSError, e: + log('bup: error: could not create tag %s: %s' % (tag_name, e)) + sys.exit(3) + +tag.write(hash.encode('hex')) +tag.close() diff --git a/lib/bup/git.py b/lib/bup/git.py index 5395393..6a51354 100644 --- a/lib/bup/git.py +++ b/lib/bup/git.py @@ -729,6 +729,33 @@ def rev_get_date(ref): raise GitError, 'no such commit %r' % ref +def rev_parse(committish): + """Resolve the full hash for 'committish', if it exists. + + Should be roughly equivalent to 'git rev-parse'. + + Returns the hex value of the hash if it is found, None if 'committish' does + not correspond to anything. + """ + head = read_ref(committish) + if head: + debug2("resolved from ref: commit = %s\n" % head.encode('hex')) + return head + + pL = PackIdxList(repo('objects/pack')) + + if len(committish) == 40: + try: + hash = committish.decode('hex') + except TypeError: + return None + + if pL.exists(hash): + return hash + + return None + + def update_ref(refname, newval, oldval): """Change the commit pointed to by a branch.""" if not oldval: diff --git a/lib/bup/vfs.py b/lib/bup/vfs.py index 1103b38..9b7158f 100644 --- a/lib/bup/vfs.py +++ b/lib/bup/vfs.py @@ -447,6 +447,24 @@ class CommitList(Node): self._subs[name] = n1 +class TagDir(Node): + """A directory that contains all tags in the repository.""" + def __init__(self, parent, name): + Node.__init__(self, parent, name, 040000, EMPTY_SHA) + + def _mksubs(self): + self._subs = {} + for (name, sha) in git.list_refs(): + if name.startswith('refs/tags/'): + name = name[10:] + date = git.rev_get_date(sha.encode('hex')) + commithex = sha.encode('hex') + target = '../.commit/%s/%s' % (commithex[:2], commithex[2:]) + tag1 = FakeSymlink(self, name, target) + tag1.ctime = tag1.mtime = date + self._subs[name] = tag1 + + class BranchList(Node): """A list of links to commits reachable by a branch in bup's repository. @@ -458,6 +476,16 @@ class BranchList(Node): def _mksubs(self): self._subs = {} + + tags = {} + for (n,c) in git.list_refs(): + if n.startswith('refs/tags/'): + name = n[10:] + if not c in tags: + tags[c] = [] + + tags[c].append(name) + revs = list(git.rev_list(self.hash.encode('hex'))) for (date, commit) in revs: l = time.localtime(date) @@ -468,6 +496,11 @@ class BranchList(Node): n1.ctime = n1.mtime = date self._subs[ls] = n1 + for tag in tags.get(commit, []): + t1 = FakeSymlink(self, tag, target) + t1.ctime = t1.mtime = date + self._subs[tag] = t1 + latest = max(revs) if latest: (date, commit) = latest @@ -496,6 +529,9 @@ class RefList(Node): commit_dir = CommitDir(self, '.commit') self._subs['.commit'] = commit_dir + tag_dir = TagDir(self, '.tag') + self._subs['.tag'] = tag_dir + for (name,sha) in git.list_refs(): if name.startswith('refs/heads/'): name = name[11:] diff --git a/main.py b/main.py index 35e28e0..feb3013 100755 --- a/main.py +++ b/main.py @@ -41,6 +41,7 @@ def usage(): on = 'Backup a remote machine to the local one', restore = 'Extract files from a backup set', save = 'Save files into a backup set (note: run "bup index" first)', + tag = 'Tag commits for easier access', web = 'Launch a web server to examine backup sets', ) diff --git a/t/test.sh b/t/test.sh index 4c05d06..cbc7f42 100755 --- a/t/test.sh +++ b/t/test.sh @@ -205,6 +205,15 @@ WVPASSEQ "$(sha1sum <$D/f)" "$(sha1sum <$D/f.new)" WVPASSEQ "$(cat $D/f.new{,} | sha1sum)" "$(sha1sum <$D/f2.new)" WVPASSEQ "$(sha1sum <$D/a)" "$(sha1sum <$D/a.new)" +WVSTART "tag" +WVFAIL bup tag -d v0.n 2>/dev/null +WVFAIL bup tag v0.n non-existant 2>/dev/null +WVPASSEQ "$(bup tag 2>&1)" "" +WVPASS bup tag v0.1 master +WVPASSEQ "$(bup tag 2>&1)" "v0.1" +WVPASS bup tag -d v0.1 + +# This section destroys data in the bup repository, so it is done last. WVSTART "fsck" WVPASS bup fsck WVPASS bup fsck --quick