]> arthur.barton.de Git - bup.git/commitdiff
add a tag command
authorGabriel Filion <lelutin@gmail.com>
Fri, 26 Nov 2010 11:00:35 +0000 (06:00 -0500)
committerAvery Pennarun <apenwarr@gmail.com>
Wed, 1 Dec 2010 10:09:53 +0000 (02:09 -0800)
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 <lelutin@gmail.com>
Documentation/bup-tag.md [new file with mode: 0644]
cmd/tag-cmd.py [new file with mode: 0755]
lib/bup/git.py
lib/bup/vfs.py
main.py
t/test.sh

diff --git a/Documentation/bup-tag.md b/Documentation/bup-tag.md
new file mode 100644 (file)
index 0000000..77b04ff
--- /dev/null
@@ -0,0 +1,64 @@
+% bup-tag(1) Bup %BUP_VERSION%
+% Gabriel Filion <lelutin@gmail.com>
+% %BUP_DATE%
+
+# NAME
+
+bup-tag - tag a commit in the bup repository
+
+# SYNOPSIS
+
+bup tag
+
+bup tag \<tag name\> \<committish\>
+
+bup tag -d \<tag name\>
+
+# 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 (executable)
index 0000000..760dce7
--- /dev/null
@@ -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 <tag name> <commit>
+bup tag -d <tag name>
+--
+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()
index 53953934c680bdb16bfad71e6f1043460566c4d2..6a51354708f3dcaa61eac472a8a38216499d612d 100644 (file)
@@ -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:
index 1103b3863fce3eb7228e5fc0dd02c2dd4b8129ac..9b7158f7a1a5b690c3d65e131ea52671a126c512 100644 (file)
@@ -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 35e28e00964f4f8c29457294cf5bd18fd5a7adbf..feb3013edc1b2ffa64adf77b74bd40d7a6159387 100755 (executable)
--- 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',
     )
 
index 4c05d06c003bc6076c680b63c915456f65f139ee..cbc7f42620255027ff2bee50338198c68cd4b1e6 100755 (executable)
--- 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