Git development
 help / color / mirror / Atom feed
* Re: [PATCH] Change softrefs file format from text (82 bytes per entry) to binary (40 bytes per entry)
From: Johannes Schindelin @ 2007-06-10  9:46 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johan Herland, git, Linus Torvalds
In-Reply-To: <7vir9w8b4a.fsf@assigned-by-dhcp.cox.net>

Hi,

On Sun, 10 Jun 2007, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > On Sat, 9 Jun 2007, Johan Herland wrote:
> >
> >> The text-based softrefs file format uses 82 bytes per entry (40 bytes 
> >> from_sha1 in hex, 1 byte SP, 40 bytes to_sha1 in hex, 1 byte LF).
> >> 
> >> The binary softrefs file format uses 40 bytes per entry (20 bytes 
> >> from_sha1, 20 bytes to_sha1).
> >> 
> >> Moving to a binary format increases performance slightly, but sacrifices 
> >> easy readability of the softrefs files.
> >
> > It is bad style to introduce one type, and then change it to another in a 
> > backwards-incompatible way. Either you make it backwards compatible, or 
> > you start with the second format, never even mentioning that you had 
> > another format.
> 
> While I agree with that in principle, I think you are being a
> bit too harsh to a set of patches that shows possible
> alternatives for an idea that is not even in any unreleased
> version of git.
> 
> Got out of the wrong side of bed this morning?

Possibly. Except it was not a bed, but an airplane passenger seat.

And it did not help that I totally disagree with the approach: "sorted 
list does not do this well, unsorted not that... let's take both!"

So, please take my words with a grain of salt. But I still think that 
_what_ I was saying is correct.

Yes, the patch series shows an alternative, but I would have wished for 
either a smaller quick-n-dirty proof-of-concept implementation ([RFC]), or 
a better thought-through one ([PATCH]).

Ciao,
Dscho

^ permalink raw reply

* Re: [PATCH 2/3] Add gitmodules(5)
From: Johannes Schindelin @ 2007-06-10  9:48 UTC (permalink / raw)
  To: Lars Hjemli; +Cc: Frank Lichtenheld, Junio C Hamano, Sven Verdoolaege, git
In-Reply-To: <8c5c35580706100158n7dabfce4y5f79f8943d8abb87@mail.gmail.com>

Hi,

On Sun, 10 Jun 2007, Lars Hjemli wrote:

> Shame on me for drinking while documenting ;-)

So I'm not the only one trying to cheer me up with some ethanol-containing 
beverage, when writing documentation?

Ciao,
Dscho

^ permalink raw reply

* [StGIT PATCH 0/6] New and improved DAG appliedness series
From: Karl Hasselström @ 2007-06-10  9:54 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git
In-Reply-To: <20070518063015.GA13516@diana.vm.bytemark.co.uk>

This took a while, but here it is. (Actually, I finished this about a
week ago, but had no Internet connection so I couldn't send it.)

The series starts with basically the same DAG appliedness test as
before, with the same known performance bug. Later on in the series,
the mechanism is changed to one that doesn't have the bug. I kept the
intermediate, slow state because the changesets read better that way,
and because the new mechanism is more complicated than the old so it
might be useful to be able to compare their output in case some bug
turns up further down the road.

To test the performance, I used a script (which I've unfortunately
misplaced) that (in the kernel repository) reset to one point 10000
commits in the past and one 5000 commits in the past, pushed a few
patches at each spot, and then created 100 applied and 100 unapplied
patches on top of upstream HEAD. This triggers the performance bug
with the first algorithm since we have unapplied commits very far from
HEAD.

Both algorithms are documented in the patches that introduce them.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply

* [StGIT PATCH 1/6] Verify patch status during the test
From: Karl Hasselström @ 2007-06-10  9:54 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git
In-Reply-To: <20070610094322.12000.56284.stgit@bill>



Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 t/t1200-push-modified.sh |   55 +++++++++++++++++++++++++++-------------------
 1 files changed, 32 insertions(+), 23 deletions(-)

diff --git a/t/t1200-push-modified.sh b/t/t1200-push-modified.sh
index 6769667..aa4ffd0 100755
--- a/t/t1200-push-modified.sh
+++ b/t/t1200-push-modified.sh
@@ -19,23 +19,26 @@ specify --merged, then rollback and retry with the correct flag.'
 test_create_repo foo
 
 test_expect_success \
-    'Clone tree and setup changes' \
-    "stg clone foo bar &&
-     (cd bar && stg new p1 -m p1
-      printf 'a\nc\n' > file && stg add file && stg refresh &&
-      stg new p2 -m p2
-      printf 'a\nb\nc\n' > file && stg refresh
-     )
-"
+    'Clone tree and setup changes' '
+    stg clone foo bar &&
+    (
+        cd bar && stg new p1 -m p1
+        printf "a\nc\n" > file && stg add file && stg refresh &&
+        stg new p2 -m p2 &&
+        printf "a\nb\nc\n" > file && stg refresh &&
+        [ "$(echo $(stg applied))" = "p1 p2" ] &&
+        [ "$(echo $(stg unapplied))" = "" ]
+    )
+'
 
 test_expect_success \
-    'Port those patches to orig tree' \
-    '(cd foo &&
-      GIT_DIR=../bar/.git git-format-patch --stdout \
-          $(cd ../bar && stg id base@master)..HEAD |
-      git-am -3 -k
-     )
-    '
+    'Port those patches to orig tree' '
+    (
+        cd foo &&
+        GIT_DIR=../bar/.git git-format-patch --stdout \
+          $(cd ../bar && stg id base@master)..HEAD | git-am -3 -k
+    )
+'
 
 test_expect_success \
     'Pull to sync with parent, preparing for the problem' \
@@ -51,15 +54,21 @@ test_expect_failure \
 "
 
 test_expect_success \
-    'Rollback the push' \
-    "(cd bar && stg push --undo
-     )
-"
+    'Rollback the push' '
+    (
+        cd bar && stg push --undo &&
+        [ "$(echo $(stg applied))" = "" ] &&
+        [ "$(echo $(stg unapplied))" = "p1 p2" ]
+    )
+'
 
 test_expect_success \
-    'Push those patches while checking they were merged upstream' \
-    "(cd bar && stg push --merged --all
-     )
-"
+    'Push those patches while checking they were merged upstream' '
+    (
+        cd bar && stg push --merged --all
+        [ "$(echo $(stg applied))" = "p1 p2" ] &&
+        [ "$(echo $(stg unapplied))" = "" ]
+    )
+'
 
 test_done

^ permalink raw reply related

* [StGIT PATCH 2/6] Make use of the get_patch() utility function
From: Karl Hasselström @ 2007-06-10  9:55 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git
In-Reply-To: <20070610094322.12000.56284.stgit@bill>

We already had it, but no one was using it

Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 stgit/stack.py |   27 +++++++++++++--------------
 1 files changed, 13 insertions(+), 14 deletions(-)

diff --git a/stgit/stack.py b/stgit/stack.py
index 7a06458..0a486bd 100644
--- a/stgit/stack.py
+++ b/stgit/stack.py
@@ -404,7 +404,7 @@ class Series(StgitObject):
         crt = self.get_current()
         if not crt:
             return None
-        return Patch(crt, self.__patch_dir, self.__refs_dir)
+        return self.get_patch(crt)
 
     def get_current(self):
         """Return the name of the topmost patch, or None if there is
@@ -678,7 +678,7 @@ class Series(StgitObject):
                 raise StackException, \
                       'Cannot delete: the series still contains patches'
             for p in patches:
-                Patch(p, self.__patch_dir, self.__refs_dir).delete()
+                self.get_patch(p).delete()
 
             # remove the trash directory
             for fname in os.listdir(self.__trash_dir):
@@ -732,7 +732,7 @@ class Series(StgitObject):
         if not name:
             raise StackException, 'No patches applied'
 
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        patch = self.get_patch(name)
 
         descr = patch.get_description()
         if not (message or descr):
@@ -798,7 +798,7 @@ class Series(StgitObject):
         name = self.get_current()
         assert(name)
 
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        patch = self.get_patch(name)
         old_bottom = patch.get_old_bottom()
         old_top = patch.get_old_top()
 
@@ -839,7 +839,7 @@ class Series(StgitObject):
         if name == None:
             name = make_patch_name(descr, self.patch_exists)
 
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        patch = self.get_patch(name)
         patch.create()
 
         if bottom:
@@ -919,7 +919,7 @@ class Series(StgitObject):
         for name in names:
             assert(name in unapplied)
 
-            patch = Patch(name, self.__patch_dir, self.__refs_dir)
+            patch = self.get_patch(name)
 
             head = top
             bottom = patch.get_bottom()
@@ -988,8 +988,7 @@ class Series(StgitObject):
         patches detected to have been applied. The state of the tree
         is restored to the original one
         """
-        patches = [Patch(name, self.__patch_dir, self.__refs_dir)
-                   for name in names]
+        patches = [self.get_patch(name) for name in names]
         patches.reverse()
 
         merged = []
@@ -1008,7 +1007,7 @@ class Series(StgitObject):
         unapplied = self.get_unapplied()
         assert(name in unapplied)
 
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        patch = self.get_patch(name)
 
         head = git.get_head()
         bottom = patch.get_bottom()
@@ -1084,7 +1083,7 @@ class Series(StgitObject):
         name = self.get_current()
         assert(name)
 
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        patch = self.get_patch(name)
         old_bottom = patch.get_old_bottom()
         old_top = patch.get_old_top()
 
@@ -1110,7 +1109,7 @@ class Series(StgitObject):
         applied.reverse()
         assert(name in applied)
 
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        patch = self.get_patch(name)
 
         if git.get_head_file() == self.get_branch():
             if keep and not git.apply_diff(git.get_head(), patch.get_bottom()):
@@ -1142,7 +1141,7 @@ class Series(StgitObject):
         """Returns True if the patch is empty
         """
         self.__patch_name_valid(name)
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        patch = self.get_patch(name)
         bottom = patch.get_bottom()
         top = patch.get_top()
 
@@ -1171,14 +1170,14 @@ class Series(StgitObject):
             self.hide_patch(newname)
 
         if oldname in unapplied:
-            Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)
+            self.get_patch(oldname).rename(newname)
             unapplied[unapplied.index(oldname)] = newname
 
             f = file(self.__unapplied_file, 'w+')
             f.writelines([line + '\n' for line in unapplied])
             f.close()
         elif oldname in applied:
-            Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)
+            self.get_patch(oldname).rename(newname)
 
             applied[applied.index(oldname)] = newname
 

^ permalink raw reply related

* [StGIT PATCH 3/6] Compute patch appliedness from commit DAG
From: Karl Hasselström @ 2007-06-10  9:55 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git
In-Reply-To: <20070610094322.12000.56284.stgit@bill>

Don't rely on cached metadata in the "applied" and "unapplied" files
to tell which patches are applied. Instead, consider the patches
reachable from the branch head to be applied, and the rest unapplied.

The order of the applied patches is also taken from the DAG, but we
can't do that for the unapplied patches. So the patch order is saved
to a file whenever it changes, and that file is consulted whenever we
need to compute the order of the unapplied patches.

The point of this excercise is to let users do things such as "git
reset" without confusing stgit. This gives incrased flexibility to
power users, and increased safety to other users. The advantages come
from the removal of redundant metadata: it is no longer possible for
StGIT's appliedness status to get out of sync with the underlying git
commit DAG.

This is how the appliedness and order is computed:

  * First, a single call to git-show-ref gives the hashes of all
    patches and the branch head.

  * Then, "git-rev-list patch1 patch2 patch3 ^branch" lists a small
    set of hashes that contains all the unapplied patches and none of
    the applied patches.

  * Last, "git-rev-list head" lists all commits in the branch. The
    applied patches are listed in the correct order.

This is efficient because none of the two rev-list calls need to look
at more than a small part of the DAG. The first call returns a small
set of commits, and the last call is abandoned before it has time to
look far back in the DAG.

Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 stgit/commands/commit.py  |    8 -
 stgit/commands/float.py   |    2 
 stgit/commands/imprt.py   |    2 
 stgit/commands/refresh.py |    2 
 stgit/commands/sync.py    |    2 
 stgit/git.py              |    5 +
 stgit/stack.py            |  333 +++++++++++++++++++++++++++++----------------
 t/t4000-upgrade.sh        |    6 +
 8 files changed, 227 insertions(+), 133 deletions(-)

diff --git a/stgit/commands/commit.py b/stgit/commands/commit.py
index 2b8d7ce..5450112 100644
--- a/stgit/commands/commit.py
+++ b/stgit/commands/commit.py
@@ -52,14 +52,8 @@ def func(parser, options, args):
     if crt_series.get_protected():
         raise CmdException, 'This branch is protected.  Commit is not permitted'
 
-    crt_head = git.get_head()
-
     out.start('Committing %d patches' % len(applied))
-
-    crt_series.pop_patch(applied[0])
-    git.switch(crt_head)
-
     for patch in applied:
-        crt_series.delete_patch(patch)
+        crt_series.delete_patch_data(patch)
 
     out.done()
diff --git a/stgit/commands/float.py b/stgit/commands/float.py
index 0e32f6b..8ba76d5 100644
--- a/stgit/commands/float.py
+++ b/stgit/commands/float.py
@@ -48,7 +48,7 @@ def func(parser, options, args):
     check_head_top_equal()
 
     unapplied = crt_series.get_unapplied()
-    applied = crt_series.get_applied()
+    applied = list(crt_series.get_applied()) # a copy, since we'll modify it
     all = unapplied + applied
 
     if options.series:
diff --git a/stgit/commands/imprt.py b/stgit/commands/imprt.py
index 0089a8b..b7a34fa 100644
--- a/stgit/commands/imprt.py
+++ b/stgit/commands/imprt.py
@@ -294,7 +294,7 @@ def __create_patch(filename, message, author_name, author_email,
         git.apply_patch(diff = diff, base = git_id(options.base))
     else:
         git.apply_patch(diff = diff)
-    crt_series.refresh_patch(edit = options.edit,
+    crt_series.refresh_patch(patch, edit = options.edit,
                              show_patch = options.showpatch)
     out.done()
 
diff --git a/stgit/commands/refresh.py b/stgit/commands/refresh.py
index 77dcbda..d560951 100644
--- a/stgit/commands/refresh.py
+++ b/stgit/commands/refresh.py
@@ -131,7 +131,7 @@ def func(parser, options, args):
 
         if autoresolved == 'yes':
             resolved_all()
-        crt_series.refresh_patch(files = args,
+        crt_series.refresh_patch(patch, files = args,
                                  message = options.message,
                                  edit = options.edit,
                                  show_patch = options.showpatch,
diff --git a/stgit/commands/sync.py b/stgit/commands/sync.py
index 8359061..a42eeac 100644
--- a/stgit/commands/sync.py
+++ b/stgit/commands/sync.py
@@ -161,7 +161,7 @@ def func(parser, options, args):
         if git.local_changes(verbose = False):
             # index (cache) already updated by the git merge. The
             # backup information was already reset above
-            crt_series.refresh_patch(cache_update = False, backup = False,
+            crt_series.refresh_patch(p, cache_update = False, backup = False,
                                      log = 'sync')
             out.done('updated')
         else:
diff --git a/stgit/git.py b/stgit/git.py
index 845c712..703425b 100644
--- a/stgit/git.py
+++ b/stgit/git.py
@@ -189,8 +189,11 @@ def _output_one_line(cmd, file_desc = None):
                                                 p.childerr.read().strip())
     return output
 
-def _output_lines(cmd):
+def _output_lines(cmd, input = []):
     p=popen2.Popen3(cmd, True)
+    for line in input:
+        p.tochild.write(line)
+    p.tochild.close()
     lines = p.fromchild.readlines()
     if p.wait():
         raise GitException, '%s failed (%s)' % (str(cmd),
diff --git a/stgit/stack.py b/stgit/stack.py
index 0a486bd..e622f68 100644
--- a/stgit/stack.py
+++ b/stgit/stack.py
@@ -18,12 +18,13 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
-import sys, os, re
+import sys, os, popen2, re
 
 from stgit.utils import *
 from stgit import git, basedir, templates
 from stgit.config import config
 from shutil import copyfile
+from sets import Set
 
 
 # stack exception class
@@ -274,7 +275,7 @@ class Patch(StgitObject):
         self.__update_log_ref(value)
 
 # The current StGIT metadata format version.
-FORMAT_VERSION = 2
+FORMAT_VERSION = 3
 
 def format_version_key(branch):
     return 'branch.%s.stgitformatversion' % branch
@@ -343,11 +344,173 @@ def update_to_current_format_version(branch, git_dir):
         rm(os.path.join(git_dir, 'refs', 'bases', branch))
         set_format_version(2)
 
+    # Update 2 -> 3.
+    if get_format_version() == 2:
+        patchorder = file(os.path.join(branch_dir, 'patchorder'), 'w')
+        for pf in ['applied', 'unapplied']:
+            pfn = os.path.join(branch_dir, pf)
+            if not os.path.isfile(pfn):
+                continue
+            for line in file(pfn):
+                line = line.strip()
+                if line:
+                    patchorder.write(line + '\n')
+            rm(pfn)
+        patchorder.close()
+        set_format_version(3)
+
     # Make sure we're at the latest version.
     if not get_format_version() in [None, FORMAT_VERSION]:
         raise StackException('Branch %s is at format version %d, expected %d'
                              % (branch, get_format_version(), FORMAT_VERSION))
 
+class PatchorderCache:
+    """An object that keeps track of the patch order for a series, as
+    saved in its patchorder file."""
+    def __init__(self, series):
+        self.__series = series
+        self.__invalidate()
+    def __invalidate(self):
+        self.__patchnames = None
+        self.__position = None
+    def __cache(self):
+        if self.__patchnames != None:
+            return # already cached
+
+        self.__patchnames = []
+        self.__position = {}
+        pof = os.path.join(self.__series._dir(), 'patchorder')
+        if os.path.isfile(pof):
+            for line in file(pof):
+                name = line.strip()
+                assert not name in self.__position
+                self.__position[name] = len(self.__patchnames)
+                self.__patchnames.append(name)
+    def set_patchorder(self, new_order):
+        self.__invalidate()
+        f = file(os.path.join(self.__series._dir(), 'patchorder'), 'w')
+        for name in new_order:
+            f.write('%s\n' % name)
+        f.close()
+    def cmp(self, name1, name2):
+        """Compare two patch names to see which patch comes first. If
+        both patches are listed in the patchorder file, sort them by
+        the order they appear there; if one is listed and the other
+        not, the listed patch goes first; and if neither is listed,
+        sort them by their names."""
+        self.__cache()
+        largepos = len(self.__patchnames)
+        pos1 = self.__position.get(name1, largepos)
+        pos2 = self.__position.get(name2, largepos)
+        return cmp((pos1, name1), (pos2, name2))
+
+def read_refs(branch):
+    """Return a mapping from patches and branch head to hashes for a
+    given branch. The patches are listed by name; the branch head is
+    None."""
+    refs = {}
+    patchpat = re.compile(r'^refs/patches/%s/([^\.]+)$' % branch)
+    for line in git._output_lines('git-show-ref'):
+        sha1, ref = line.split()
+        m = re.match(patchpat, ref)
+        if m:
+            refs[m.group(1)] = sha1
+        elif ref == 'refs/heads/%s' % branch:
+            refs[None] = sha1
+    return refs
+
+def unapplied_patches(ref2hash):
+    """Given a map of patch names (and the branch head, keyed by None)
+    to hashes, return the set of unapplied patches."""
+    hash2refs = {}
+    for r, h in ref2hash.iteritems():
+        hash2refs.setdefault(h, Set()).add(r)
+
+    unapplied = Set()
+    for line in git._output_lines(
+        'git-rev-list --stdin',
+        ('%s%s\n' % (['', '^'][ref == None], sha1)
+         for ref, sha1 in ref2hash.iteritems())):
+        for ref in hash2refs.get(line.strip(), []):
+            unapplied.add(ref)
+    return unapplied
+
+def sort_applied_patches(ref2hash):
+    """Given a map of patch names (and the branch head, keyed by None)
+    to hashes, return a list with the applied patches in stack order.
+    All patches in the map must be applied."""
+    hash2refs = {}
+    for r, h in ref2hash.iteritems():
+        if r != None:
+            hash2refs.setdefault(h, Set()).add(r)
+
+    missing = Set(ref for ref in ref2hash.iterkeys() if ref != None)
+    if not missing:
+        return []
+    applied = []
+    grl = popen2.Popen3('git-rev-list %s' % ref2hash[None], True)
+    for line in grl.fromchild:
+        for ref in hash2refs.get(line.strip(), []):
+            applied.append(ref)
+            missing.remove(ref)
+        if not missing:
+            applied.reverse()
+            return applied
+
+    raise StackException, 'Could not find patches: %s' % ', '.join(missing)
+
+class AppliedCache:
+    """An object that keeps track of the appliedness and order of the
+    patches in a patch series."""
+    def __init__(self, series):
+        self.__series = series
+        self.__order = PatchorderCache(series)
+        self.__invalidate()
+    def get_applied(self):
+        self.__cache()
+        return self.__applied
+    def get_unapplied(self):
+        self.__cache()
+        return self.__unapplied
+    def rename(self, oldname, newname):
+        """Rename a patch."""
+        self.__cache()
+        for lst in (self.__applied, self.__unapplied):
+            try:
+                lst[lst.index(oldname)] = newname
+            except ValueError:
+                pass # lst.index() couldn't find the index
+            else:
+                self.__write_patchorder()
+                return
+        raise StackException, 'Unknown patch "%s"' % oldname
+    def __write_patchorder(self):
+        self.__order.set_patchorder(self.get_applied() + self.get_unapplied())
+    def set_patchorder(self, new_order):
+        self.__order.set_patchorder(new_order)
+        self.refresh()
+    def refresh(self):
+        """Re-read patch appliedness info, and write patch order to
+        disk."""
+        self.__invalidate()
+        self.__write_patchorder()
+    def __invalidate(self):
+        self.__applied = None
+        self.__unapplied = None
+    def __cached(self):
+        return (self.__applied != None)
+    def __cache(self):
+        if self.__cached():
+            return
+        patches = read_refs(self.__series.get_branch())
+        unapplied = unapplied_patches(patches)
+        for patch in unapplied:
+            del patches[patch]
+        self.__applied = sort_applied_patches(patches)
+        self.__unapplied = list(unapplied)
+        self.__unapplied.sort(self.__order.cmp)
+
+
 class Series(StgitObject):
     """Class including the operations on series
     """
@@ -372,8 +535,6 @@ class Series(StgitObject):
         self.__refs_dir = os.path.join(self.__base_dir, 'refs', 'patches',
                                        self.__name)
 
-        self.__applied_file = os.path.join(self._dir(), 'applied')
-        self.__unapplied_file = os.path.join(self._dir(), 'unapplied')
         self.__hidden_file = os.path.join(self._dir(), 'hidden')
 
         # where this series keeps its patches
@@ -382,6 +543,8 @@ class Series(StgitObject):
         # trash directory
         self.__trash_dir = os.path.join(self._dir(), 'trash')
 
+        self.__applied_cache = AppliedCache(self)
+
     def __patch_name_valid(self, name):
         """Raise an exception if the patch name is not valid.
         """
@@ -409,11 +572,7 @@ class Series(StgitObject):
     def get_current(self):
         """Return the name of the topmost patch, or None if there is
         no such patch."""
-        try:
-            applied = self.get_applied()
-        except StackException:
-            # No "applied" file: branch is not initialized.
-            return None
+        applied = self.get_applied()
         try:
             return applied[-1]
         except IndexError:
@@ -421,20 +580,10 @@ class Series(StgitObject):
             return None
 
     def get_applied(self):
-        if not os.path.isfile(self.__applied_file):
-            raise StackException, 'Branch "%s" not initialised' % self.__name
-        f = file(self.__applied_file)
-        names = [line.strip() for line in f.readlines()]
-        f.close()
-        return names
+        return self.__applied_cache.get_applied()
 
     def get_unapplied(self):
-        if not os.path.isfile(self.__unapplied_file):
-            raise StackException, 'Branch "%s" not initialised' % self.__name
-        f = file(self.__unapplied_file)
-        names = [line.strip() for line in f.readlines()]
-        f.close()
-        return names
+        return self.__applied_cache.get_unapplied()
 
     def get_hidden(self):
         if not os.path.isfile(self.__hidden_file):
@@ -446,12 +595,12 @@ class Series(StgitObject):
 
     def get_base(self):
         # Return the parent of the bottommost patch, if there is one.
-        if os.path.isfile(self.__applied_file):
-            bottommost = file(self.__applied_file).readline().strip()
-            if bottommost:
-                return self.get_patch(bottommost).get_bottom()
-        # No bottommost patch, so just return HEAD
-        return git.get_head()
+        applied = self.get_applied()
+        if applied:
+            return self.get_patch(applied[0]).get_bottom()
+        else:
+            # No bottommost patch, so just return HEAD
+            return git.get_head()
 
     def get_head(self):
         """Return the head of the branch
@@ -585,8 +734,6 @@ class Series(StgitObject):
 
         self.set_parent(parent_remote, parent_branch)
 
-        self.create_empty_field('applied')
-        self.create_empty_field('unapplied')
         os.makedirs(self.__refs_dir)
         self._set_field('orig-base', git.get_head())
 
@@ -687,10 +834,6 @@ class Series(StgitObject):
 
             # FIXME: find a way to get rid of those manual removals
             # (move functionality to StgitObject ?)
-            if os.path.exists(self.__applied_file):
-                os.remove(self.__applied_file)
-            if os.path.exists(self.__unapplied_file):
-                os.remove(self.__unapplied_file)
             if os.path.exists(self.__hidden_file):
                 os.remove(self.__hidden_file)
             if os.path.exists(self._dir()+'/orig-base'):
@@ -719,7 +862,7 @@ class Series(StgitObject):
         config.unset('branch.%s.merge' % self.__name)
         config.unset('branch.%s.stgit.parentbranch' % self.__name)
 
-    def refresh_patch(self, files = None, message = None, edit = False,
+    def refresh_patch(self, name, files = None, message = None, edit = False,
                       show_patch = False,
                       cache_update = True,
                       author_name = None, author_email = None,
@@ -728,10 +871,6 @@ class Series(StgitObject):
                       backup = False, sign_str = None, log = 'refresh'):
         """Generates a new commit for the given patch
         """
-        name = self.get_current()
-        if not name:
-            raise StackException, 'No patches applied'
-
         patch = self.get_patch(name)
 
         descr = patch.get_description()
@@ -821,9 +960,10 @@ class Series(StgitObject):
         """Creates a new patch
         """
 
+        appl, unappl = self.get_applied(), self.get_unapplied()
         if name != None:
             self.__patch_name_valid(name)
-            if self.patch_applied(name) or self.patch_unapplied(name):
+            if name in appl or name in unappl:
                 raise StackException, 'Patch "%s" already exists' % name
 
         if not message and can_edit:
@@ -860,20 +1000,29 @@ class Series(StgitObject):
 
         if unapplied:
             self.log_patch(patch, 'new')
-
-            patches = [patch.get_name()] + self.get_unapplied()
-
-            f = file(self.__unapplied_file, 'w+')
-            f.writelines([line + '\n' for line in patches])
-            f.close()
+            order = appl + [patch.get_name()] + unappl
         elif before_existing:
             self.log_patch(patch, 'new')
-
-            insert_string(self.__applied_file, patch.get_name())
+            order = [patch.get_name()] + appl + unappl
         else:
-            append_string(self.__applied_file, patch.get_name())
+            order = appl + [patch.get_name()] + unappl
             if refresh:
-                self.refresh_patch(cache_update = False, log = 'new')
+                self.refresh_patch(name, cache_update = False, log = 'new')
+        self.__applied_cache.set_patchorder(order)
+
+        return patch
+
+
+    def delete_patch_data(self, name):
+        """Deletes the stgit data for a patch."""
+        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+
+        # save the commit id to a trash file
+        write_string(os.path.join(self.__trash_dir, name), patch.get_top())
+
+        patch.delete()
+        if self.patch_hidden(name):
+            self.unhide_patch(name)
 
         return patch
 
@@ -881,9 +1030,8 @@ class Series(StgitObject):
         """Deletes a patch
         """
         self.__patch_name_valid(name)
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
 
-        if self.__patch_is_current(patch):
+        if self.get_current() == name:
             self.pop_patch(name)
         elif self.patch_applied(name):
             raise StackException, 'Cannot remove an applied patch, "%s", ' \
@@ -891,19 +1039,8 @@ class Series(StgitObject):
         elif not name in self.get_unapplied():
             raise StackException, 'Unknown patch "%s"' % name
 
-        # save the commit id to a trash file
-        write_string(os.path.join(self.__trash_dir, name), patch.get_top())
-
-        patch.delete()
-
-        unapplied = self.get_unapplied()
-        unapplied.remove(name)
-        f = file(self.__unapplied_file, 'w+')
-        f.writelines([line + '\n' for line in unapplied])
-        f.close()
-
-        if self.patch_hidden(name):
-            self.unhide_patch(name)
+        self.delete_patch_data(name)
+        self.__applied_cache.refresh()
 
     def forward_patches(self, names):
         """Try to fast-forward an array of patches.
@@ -967,19 +1104,12 @@ class Series(StgitObject):
                     break
 
             forwarded+=1
-            unapplied.remove(name)
 
         if forwarded == 0:
             return 0
 
         git.switch(top)
-
-        append_strings(self.__applied_file, names[0:forwarded])
-
-        f = file(self.__unapplied_file, 'w+')
-        f.writelines([line + '\n' for line in unapplied])
-        f.close()
-
+        self.__applied_cache.refresh()
         return forwarded
 
     def merged_patches(self, names):
@@ -1052,13 +1182,6 @@ class Series(StgitObject):
                               'Use "refresh" after fixing the conflicts or'
                               ' revert the operation with "push --undo".')
 
-        append_string(self.__applied_file, name)
-
-        unapplied.remove(name)
-        f = file(self.__unapplied_file, 'w+')
-        f.writelines([line + '\n' for line in unapplied])
-        f.close()
-
         # head == bottom case doesn't need to refresh the patch
         if empty or head != bottom:
             if not ex:
@@ -1068,15 +1191,17 @@ class Series(StgitObject):
                     log = 'push(m)'
                 else:
                     log = 'push'
-                self.refresh_patch(cache_update = False, log = log)
+                self.refresh_patch(name, cache_update = False, log = log)
             else:
                 # we store the correctly merged files only for
                 # tracking the conflict history. Note that the
                 # git.merge() operations should always leave the index
                 # in a valid state (i.e. only stage 0 files)
-                self.refresh_patch(cache_update = False, log = 'push(c)')
+                self.refresh_patch(name, cache_update = False, log = 'push(c)')
                 raise StackException, str(ex)
 
+        self.__applied_cache.refresh()
+
         return modified
 
     def undo_push(self):
@@ -1105,10 +1230,7 @@ class Series(StgitObject):
     def pop_patch(self, name, keep = False):
         """Pops the top patch from the stack
         """
-        applied = self.get_applied()
-        applied.reverse()
-        assert(name in applied)
-
+        assert(name in self.get_applied())
         patch = self.get_patch(name)
 
         if git.get_head_file() == self.get_branch():
@@ -1118,24 +1240,7 @@ class Series(StgitObject):
             git.switch(patch.get_bottom(), keep)
         else:
             git.set_branch(self.get_branch(), patch.get_bottom())
-
-        # save the new applied list
-        idx = applied.index(name) + 1
-
-        popped = applied[:idx]
-        popped.reverse()
-        unapplied = popped + self.get_unapplied()
-
-        f = file(self.__unapplied_file, 'w+')
-        f.writelines([line + '\n' for line in unapplied])
-        f.close()
-
-        del applied[:idx]
-        applied.reverse()
-
-        f = file(self.__applied_file, 'w+')
-        f.writelines([line + '\n' for line in applied])
-        f.close()
+        self.__applied_cache.refresh()
 
     def empty_patch(self, name):
         """Returns True if the patch is empty
@@ -1161,7 +1266,8 @@ class Series(StgitObject):
 
         if oldname == newname:
             raise StackException, '"To" name and "from" name are the same'
-
+        if oldname in applied or oldname in unapplied:
+            raise StackException, 'Unknown patch "%s"' % oldname
         if newname in applied or newname in unapplied:
             raise StackException, 'Patch "%s" already exists' % newname
 
@@ -1169,23 +1275,8 @@ class Series(StgitObject):
             self.unhide_patch(oldname)
             self.hide_patch(newname)
 
-        if oldname in unapplied:
-            self.get_patch(oldname).rename(newname)
-            unapplied[unapplied.index(oldname)] = newname
-
-            f = file(self.__unapplied_file, 'w+')
-            f.writelines([line + '\n' for line in unapplied])
-            f.close()
-        elif oldname in applied:
-            self.get_patch(oldname).rename(newname)
-
-            applied[applied.index(oldname)] = newname
-
-            f = file(self.__applied_file, 'w+')
-            f.writelines([line + '\n' for line in applied])
-            f.close()
-        else:
-            raise StackException, 'Unknown patch "%s"' % oldname
+        self.get_patch(oldname).rename(newname)
+        self.__applied_cache.rename(oldname, newname)
 
     def log_patch(self, patch, message):
         """Generate a log commit for a patch
diff --git a/t/t4000-upgrade.sh b/t/t4000-upgrade.sh
index 8a308fb..01be50d 100755
--- a/t/t4000-upgrade.sh
+++ b/t/t4000-upgrade.sh
@@ -34,6 +34,12 @@ for ver in 0.12 0.8; do
         ! git show-ref --verify --quiet refs/bases/master
     '
 
+    test_expect_success \
+        "v$ver: Make sure the applied and unapplied files are gone" '
+        [ ! -e .git/patches/master/applied ] &&
+        [ ! -e .git/patches/master/unapplied ]
+'
+
     cd ..
 done
 

^ permalink raw reply related

* [StGIT PATCH 4/6] Test the new DAG appliedness machinery
From: Karl Hasselström @ 2007-06-10  9:55 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git
In-Reply-To: <20070610094322.12000.56284.stgit@bill>



Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 t/t3000-git-interop.sh |   60 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 60 insertions(+), 0 deletions(-)

diff --git a/t/t3000-git-interop.sh b/t/t3000-git-interop.sh
new file mode 100755
index 0000000..44414b9
--- /dev/null
+++ b/t/t3000-git-interop.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+# Copyright (c) 2007 Karl Hasselström
+test_description='Test git/StGIT interoperability'
+. ./test-lib.sh
+
+test_expect_success \
+    'Create some git-only history' '
+    echo foo > foo.txt &&
+    git add foo.txt &&
+    git commit -a -m foo &&
+    git tag foo-tag &&
+    for i in 0 1 2 3 4; do
+        echo foo$i >> foo.txt &&
+        git commit -a -m foo$i;
+    done
+'
+
+test_expect_success \
+    'Initialize the StGIT repository' '
+    stg init
+'
+
+test_expect_success \
+    'Create five patches' '
+    for i in 0 1 2 3 4; do
+        stg new p$i -m p$i;
+    done &&
+    [ "$(echo $(stg applied))" = "p0 p1 p2 p3 p4" ] &&
+    [ "$(echo $(stg unapplied))" = "" ]
+'
+
+test_expect_success \
+    'Pop two patches with git-reset' '
+    git reset --hard HEAD~2 &&
+    [ "$(echo $(stg applied))" = "p0 p1 p2" ] &&
+    [ "$(echo $(stg unapplied))" = "p3 p4" ]
+'
+
+test_expect_success \
+    'Create a new patch' '
+    stg new q0 -m q0 &&
+    [ "$(echo $(stg applied))" = "p0 p1 p2 q0" ] &&
+    [ "$(echo $(stg unapplied))" = "p3 p4" ]
+'
+
+test_expect_success \
+    'Go to an unapplied patch with with git-reset' '
+    git reset --hard $(stg id p3) &&
+    [ "$(echo $(stg applied))" = "p0 p1 p2 p3" ] &&
+    [ "$(echo $(stg unapplied))" = "q0 p4" ]
+'
+
+test_expect_success \
+    'Go back to below the stack base with git-reset' '
+    git reset --hard foo-tag &&
+    [ "$(echo $(stg applied))" = "" ] &&
+    [ "$(echo $(stg unapplied))" = "p0 p1 p2 q0 p3 p4" ]
+'
+
+test_done

^ permalink raw reply related

* [StGIT PATCH 5/6] Fix bash completion after the DAG appliedness patch
From: Karl Hasselström @ 2007-06-10  9:55 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git
In-Reply-To: <20070610094322.12000.56284.stgit@bill>

The bash tab completion used the "applied", "unapplied" and "current"
files to generate completions. Since these don't exist anymore, use
stg applied/unapplied/series to obtain the same info. It's a bit
slower, but not terribly much so.

Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 contrib/stgit-completion.bash |   15 ++++-----------
 1 files changed, 4 insertions(+), 11 deletions(-)

diff --git a/contrib/stgit-completion.bash b/contrib/stgit-completion.bash
index d497098..2b9722a 100644
--- a/contrib/stgit-completion.bash
+++ b/contrib/stgit-completion.bash
@@ -70,32 +70,25 @@ _current_branch ()
 # List of all applied patches.
 _applied_patches ()
 {
-    local g=$(_gitdir)
-    [ "$g" ] && cat "$g/patches/$(_current_branch)/applied"
+    stg applied 2> /dev/null
 }
 
 # List of all unapplied patches.
 _unapplied_patches ()
 {
-    local g=$(_gitdir)
-    [ "$g" ] && cat "$g/patches/$(_current_branch)/unapplied"
+    stg unapplied 2> /dev/null
 }
 
 # List of all patches.
 _all_patches ()
 {
-    local b=$(_current_branch)
-    local g=$(_gitdir)
-    [ "$g" ] && cat "$g/patches/$b/applied" "$g/patches/$b/unapplied"
+    stg series --noprefix 2> /dev/null
 }
 
 # List of all patches except the current patch.
 _all_other_patches ()
 {
-    local b=$(_current_branch)
-    local g=$(_gitdir)
-    [ "$g" ] && cat "$g/patches/$b/applied" "$g/patches/$b/unapplied" \
-        | grep -v "^$(cat $g/patches/$b/current 2> /dev/null)$"
+    stg series 2> /dev/null | grep -v '^>' | cut -f 2 -d ' '
 }
 
 _all_branches ()

^ permalink raw reply related

* [StGIT PATCH 6/6] Speed up the appliedness test
From: Karl Hasselström @ 2007-06-10  9:55 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git
In-Reply-To: <20070610094322.12000.56284.stgit@bill>

The appliedness test was too slow if at least one patch, applied or
unapplied, was too far away from HEAD, since we had to visit the whole
intervening part of the commit DAG.

This patch fixes that problem by maintaining a cache of uninteresting
commits that are known to not reach any patches in the commit DAG.
(Specifically, this is at all times the set of commits that are
parents to patch commits and do not have a patch commit as their
ancestor.) By exlcuding these commits when walking the graph, we only
have to visit the interesting places.

As a nice side effect, the cache of uninteresting commits makes it
possible to use just one git-rev-list call instead of two, since we
can list the applied patches without first computing the unapplied
patches; the unapplied patches are then simply all patches except
those that are applied.

Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 stgit/stack.py |  278 +++++++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 223 insertions(+), 55 deletions(-)

diff --git a/stgit/stack.py b/stgit/stack.py
index e622f68..6fd1a2a 100644
--- a/stgit/stack.py
+++ b/stgit/stack.py
@@ -156,7 +156,7 @@ class Patch(StgitObject):
         os.mkdir(self._dir())
         self.create_empty_field('bottom')
         self.create_empty_field('top')
-
+ 
     def delete(self):
         for f in os.listdir(self._dir()):
             os.remove(os.path.join(self._dir(), f))
@@ -369,7 +369,11 @@ class PatchorderCache:
     saved in its patchorder file."""
     def __init__(self, series):
         self.__series = series
+        self.__file = os.path.join(self.__series._dir(), 'patchorder')
         self.__invalidate()
+    def delete_file(self):
+        if os.path.isfile(self.__file):
+            os.remove(self.__file)
     def __invalidate(self):
         self.__patchnames = None
         self.__position = None
@@ -379,9 +383,8 @@ class PatchorderCache:
 
         self.__patchnames = []
         self.__position = {}
-        pof = os.path.join(self.__series._dir(), 'patchorder')
-        if os.path.isfile(pof):
-            for line in file(pof):
+        if os.path.isfile(self.__file):
+            for line in file(self.__file):
                 name = line.strip()
                 assert not name in self.__position
                 self.__position[name] = len(self.__patchnames)
@@ -404,60 +407,200 @@ class PatchorderCache:
         pos2 = self.__position.get(name2, largepos)
         return cmp((pos1, name1), (pos2, name2))
 
+class UninterestingCache:
+    """Keeps track of a set of commits that do not reach any patches.
+    These are used to speed up the detection of unapplied patches.
+
+    Specifically, this is at all times the set of commits c that
+    fulfill the following two criteria:
+
+      * c does not reach any patch
+
+      * c is the parent of a patch
+
+    """
+    def __init__(self, series):
+        self.__series = series
+        self.__uninteresting = None
+        self.__filename = os.path.join(self.__series._dir(), 'uninteresting')
+    def __invalidate(self):
+        self.__uninteresting = None
+        self.delete_file()
+    def delete_file(self):
+        if os.path.isfile(self.__filename):
+            os.remove(self.__filename)
+    def __other_patches(self, patchname):
+        """All patches except the named one."""
+        ref2hash = read_refs(self.__series.get_branch())
+        return [self.__series.get_patch(ref)
+                for ref in ref2hash.iterkeys()
+                if ref and ref != patchname]
+    def __write_file(self):
+        """Write the uninteresting commits to file."""
+        try:
+            f = file(self.__filename, 'w')
+            for u in self.__uninteresting:
+                f.write('%s\n' % u)
+            f.close()
+        except IOError:
+            pass # this isn't fatal -- the directory is probably missing
+    def __read_file(self):
+        """Read the uninteresting commits from file. Return true on
+        success, false on failure."""
+        if not os.path.isfile(self.__filename):
+            return False
+        self.__uninteresting = Set()
+        for line in file(self.__filename):
+            self.__uninteresting.add(line.strip())
+        return True
+    def __cache_file(self):
+        """Try to cache the uninteresting commits using only the cache
+        file. Return true on success, false on failure."""
+        if self.__uninteresting != None:
+            return True # already cached
+        return self.__read_file()
+    def __cache(self):
+        """Cache the uninteresting commits, recomputing them if
+        necessary."""
+        if self.__cache_file():
+            return
+        self.__compute_uninteresting()
+        self.__write_file()
+    def __compute_uninteresting(self):
+        """Compute a reasonable set of uninteresting commits from
+        scratch. This is expensive."""
+        out.start('Finding uninteresting commits')
+        ref2hash = read_refs(self.__series.get_branch())
+        patches = Set([sha1 for ref, sha1 in ref2hash.iteritems() if ref])
+        interesting, uninteresting = Set(), Set()
+
+        # Iterate over all commits. We are guaranteed to see each
+        # commit before any of its children.
+        for line in git._output_lines(
+            'git-rev-list --topo-order --reverse --parents --all'):
+            commits = line.split()
+            commit, parents = commits[0], Set(commits[1:])
+
+            # Patches are interesting.
+            if commit in patches:
+                interesting.add(commit)
+
+                # The parents of a patch are uninteresting unless they
+                # are interesting.
+                for p in parents:
+                    if not p in interesting:
+                        uninteresting.add(p)
+                continue
+
+            # Commits with interesting parents are interesting.
+            if interesting.intersection(parents):
+                interesting.add(commit)
+        self.__uninteresting = uninteresting
+        out.done()
+    def create_patch(self, name, top, bottom):
+        """The given patch has been created. Update the uninterested
+        state to maintain the invariant."""
+        if not self.__cache_file():
+            return # not cached
+
+        # New patch inserted just below an existing bottommost patch:
+        # need to move the uninteresting commit down one step.
+        if top in self.__uninteresting:
+            self.__uninteresting.remove(top)
+            self.__uninteresting.add(bottom)
+            self.__write_file()
+            return
+
+        # New patch inserted next to an existing non-bottommost patch:
+        # don't need to do anything.
+        existing_patches = self.__other_patches(name)
+        tops = Set([p.get_top() for p in existing_patches])
+        bottoms = Set([p.get_bottom() for p in existing_patches])
+        if bottom in bottoms or bottom in tops or top in bottoms:
+            return
+
+        # The new patch is not adjacent to an existing patch. We'd
+        # need to first get rid of any uninteresting commit that
+        # reaches this patch, and then mark the patch's bottom
+        # uninteresting if it doesn't reach any other patch. This is a
+        # lot of work, so we chicken out and blow the whole cache
+        # instead.
+        self.__invalidate()
+    def delete_patch(self, name, top, bottom):
+        """The given patch has been deleted. Update the uninterested
+        state to maintain the invariant."""
+        if not self.__cache_file():
+            return # not cached
+
+        # If this patch reaches another patch, there's nothing to do.
+        if not bottom in self.__uninteresting:
+            return
+
+        # If another patch has the same bottom, it's still
+        # uninteresting and there's nothing more to do.
+        other_patches = self.__other_patches(name)
+        for p in other_patches:
+            if p.get_bottom() == bottom:
+                return
+
+        # If there are other patches on top of this one, their bottoms
+        # (this patch's top) become uninteresting in place of this
+        # patch's bottom.
+        for p in other_patches:
+            if p.get_bottom() == top:
+                self.__uninteresting.remove(bottom)
+                self.__uninteresting.add(top)
+                self.__write_file()
+                return
+
+        # The bottom of this patch is no longer uninteresting. But
+        # there might be other patches that reach it, whose bottoms
+        # would need to be marked uninteresting. That would require an
+        # expensive reachability analysis.
+        self.__invalidate()
+    def get(self):
+        self.__cache()
+        return self.__uninteresting
+
 def read_refs(branch):
     """Return a mapping from patches and branch head to hashes for a
     given branch. The patches are listed by name; the branch head is
     None."""
     refs = {}
     patchpat = re.compile(r'^refs/patches/%s/([^\.]+)$' % branch)
+    head = 'refs/heads/%s' % branch
     for line in git._output_lines('git-show-ref'):
         sha1, ref = line.split()
         m = re.match(patchpat, ref)
         if m:
             refs[m.group(1)] = sha1
-        elif ref == 'refs/heads/%s' % branch:
+        elif ref == head:
             refs[None] = sha1
+    if not None in refs:
+        raise StackException, 'Could not find %s' % head
     return refs
 
-def unapplied_patches(ref2hash):
+def get_patches(ref2hash, uninteresting):
     """Given a map of patch names (and the branch head, keyed by None)
-    to hashes, return the set of unapplied patches."""
-    hash2refs = {}
-    for r, h in ref2hash.iteritems():
-        hash2refs.setdefault(h, Set()).add(r)
-
+    to hashes, return the list of applied patches and the set of
+    unapplied patches. The second parameter is a set of commit objects
+    that do not reach any patch."""
+    applied = []
     unapplied = Set()
-    for line in git._output_lines(
-        'git-rev-list --stdin',
-        ('%s%s\n' % (['', '^'][ref == None], sha1)
-         for ref, sha1 in ref2hash.iteritems())):
-        for ref in hash2refs.get(line.strip(), []):
-            unapplied.add(ref)
-    return unapplied
-
-def sort_applied_patches(ref2hash):
-    """Given a map of patch names (and the branch head, keyed by None)
-    to hashes, return a list with the applied patches in stack order.
-    All patches in the map must be applied."""
-    hash2refs = {}
+    hash2patches = {}
     for r, h in ref2hash.iteritems():
-        if r != None:
-            hash2refs.setdefault(h, Set()).add(r)
+        if r:
+            hash2patches.setdefault(h, Set()).add(r)
+            unapplied.add(r)
 
-    missing = Set(ref for ref in ref2hash.iterkeys() if ref != None)
-    if not missing:
-        return []
-    applied = []
-    grl = popen2.Popen3('git-rev-list %s' % ref2hash[None], True)
-    for line in grl.fromchild:
-        for ref in hash2refs.get(line.strip(), []):
+    for line in git._output_lines(
+        'git-rev-list --topo-order --stdin', ['%s\n' % ref2hash[None]]
+        + ['^%s\n' % u for u in uninteresting]):
+        for ref in hash2patches.get(line.strip(), []):
             applied.append(ref)
-            missing.remove(ref)
-        if not missing:
-            applied.reverse()
-            return applied
-
-    raise StackException, 'Could not find patches: %s' % ', '.join(missing)
+            unapplied.remove(ref)
+    applied.reverse()
+    return applied, unapplied
 
 class AppliedCache:
     """An object that keeps track of the appliedness and order of the
@@ -465,7 +608,11 @@ class AppliedCache:
     def __init__(self, series):
         self.__series = series
         self.__order = PatchorderCache(series)
+        self.__uninteresting = UninterestingCache(series)
         self.__invalidate()
+    def delete_files(self):
+        for sub in [self.__uninteresting, self.__order]:
+            sub.delete_file()
     def get_applied(self):
         self.__cache()
         return self.__applied
@@ -484,6 +631,17 @@ class AppliedCache:
                 self.__write_patchorder()
                 return
         raise StackException, 'Unknown patch "%s"' % oldname
+    def new(self, name, top, bottom):
+        """Create new patch."""
+        self.__uninteresting.create_patch(name, top, bottom)
+    def delete(self, name, top, bottom):
+        """Delete a patch."""
+        self.__uninteresting.delete_patch(name, top, bottom)
+    def change(self, name, old_top, old_bottom, new_top, new_bottom):
+        """Change a patch."""
+        if (new_top, new_bottom) != (old_top, old_bottom):
+            self.new(name, new_top, new_bottom)
+            self.delete(name, old_top, old_bottom)
     def __write_patchorder(self):
         self.__order.set_patchorder(self.get_applied() + self.get_unapplied())
     def set_patchorder(self, new_order):
@@ -502,11 +660,8 @@ class AppliedCache:
     def __cache(self):
         if self.__cached():
             return
-        patches = read_refs(self.__series.get_branch())
-        unapplied = unapplied_patches(patches)
-        for patch in unapplied:
-            del patches[patch]
-        self.__applied = sort_applied_patches(patches)
+        self.__applied, unapplied = get_patches(
+            read_refs(self.__series.get_branch()), self.__uninteresting.get())
         self.__unapplied = list(unapplied)
         self.__unapplied.sort(self.__order.cmp)
 
@@ -838,6 +993,7 @@ class Series(StgitObject):
                 os.remove(self.__hidden_file)
             if os.path.exists(self._dir()+'/orig-base'):
                 os.remove(self._dir()+'/orig-base')
+            self.__applied_cache.delete_files()
 
             if not os.listdir(self.__patch_dir):
                 os.rmdir(self.__patch_dir)
@@ -940,16 +1096,20 @@ class Series(StgitObject):
         patch = self.get_patch(name)
         old_bottom = patch.get_old_bottom()
         old_top = patch.get_old_top()
+        curr_bottom = patch.get_bottom()
+        curr_top = patch.get_top()
 
         # the bottom of the patch is not changed by refresh. If the
         # old_bottom is different, there wasn't any previous 'refresh'
         # command (probably only a 'push')
-        if old_bottom != patch.get_bottom() or old_top == patch.get_top():
+        if old_bottom != curr_bottom or old_top == curr_top:
             raise StackException, 'No undo information available'
 
         git.reset(tree_id = old_top, check_out = False)
         if patch.restore_old_boundaries():
             self.log_patch(patch, 'undo')
+        self.__applied_cache.change(name, curr_top, curr_bottom,
+                                    old_top, old_bottom)
 
     def new_patch(self, name, message = None, can_edit = True,
                   unapplied = False, show_patch = False,
@@ -982,14 +1142,11 @@ class Series(StgitObject):
         patch = self.get_patch(name)
         patch.create()
 
-        if bottom:
-            patch.set_bottom(bottom)
-        else:
-            patch.set_bottom(head)
-        if top:
-            patch.set_top(top)
-        else:
-            patch.set_top(head)
+        bottom = bottom or head
+        top = top or head
+        patch.set_bottom(bottom)
+        patch.set_top(top)
+        self.__applied_cache.new(name, top, bottom)
 
         patch.set_description(descr)
         patch.set_authname(author_name)
@@ -1016,15 +1173,16 @@ class Series(StgitObject):
     def delete_patch_data(self, name):
         """Deletes the stgit data for a patch."""
         patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        top, bottom = patch.get_top(), patch.get_bottom()
 
         # save the commit id to a trash file
-        write_string(os.path.join(self.__trash_dir, name), patch.get_top())
+        write_string(os.path.join(self.__trash_dir, name), top)
 
         patch.delete()
         if self.patch_hidden(name):
             self.unhide_patch(name)
 
-        return patch
+        self.__applied_cache.delete(name, top, bottom)
 
     def delete_patch(self, name):
         """Deletes a patch
@@ -1084,6 +1242,7 @@ class Series(StgitObject):
 
                     top_tree = git.get_commit(top).get_tree()
 
+                    old_top = top
                     top = git.commit(message = descr, parents = [head],
                                      cache_update = False,
                                      tree_id = top_tree,
@@ -1097,6 +1256,9 @@ class Series(StgitObject):
                     patch.set_bottom(head, backup = True)
                     patch.set_top(top, backup = True)
 
+                    self.__applied_cache.change(
+                        name, old_top = old_top, old_bottom = bottom,
+                        new_top = top, new_bottom = head)
                     self.log_patch(patch, 'push(f)')
                 else:
                     top = head
@@ -1154,6 +1316,7 @@ class Series(StgitObject):
             # need an empty commit
             patch.set_bottom(head, backup = True)
             patch.set_top(head, backup = True)
+            self.__applied_cache.change(name, top, bottom, head, head)
             modified = True
         elif head == bottom:
             # reset the backup information. No need for logging
@@ -1166,6 +1329,7 @@ class Series(StgitObject):
             # The current patch is empty after merge.
             patch.set_bottom(head, backup = True)
             patch.set_top(head, backup = True)
+            self.__applied_cache.change(name, top, bottom, head, head)
 
             # Try the fast applying first. If this fails, fall back to the
             # three-way merge
@@ -1211,6 +1375,8 @@ class Series(StgitObject):
         patch = self.get_patch(name)
         old_bottom = patch.get_old_bottom()
         old_top = patch.get_old_top()
+        curr_bottom = patch.get_bottom()
+        curr_top = patch.get_top()
 
         # the top of the patch is changed by a push operation only
         # together with the bottom (otherwise the top was probably
@@ -1222,6 +1388,8 @@ class Series(StgitObject):
         git.reset()
         self.pop_patch(name)
         ret = patch.restore_old_boundaries()
+        self.__applied_cache.change(name, curr_top, curr_bottom,
+                                    old_top, old_bottom)
         if ret:
             self.log_patch(patch, 'undo')
 

^ permalink raw reply related

* Re: [PATCH 2/3] Add gitmodules(5)
From: Lars Hjemli @ 2007-06-10 10:10 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Frank Lichtenheld, Junio C Hamano, Sven Verdoolaege, git
In-Reply-To: <Pine.LNX.4.64.0706101048120.4059@racer.site>

On 6/10/07, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
> Hi,
>
> On Sun, 10 Jun 2007, Lars Hjemli wrote:
>
> > Shame on me for drinking while documenting ;-)
>
> So I'm not the only one trying to cheer me up with some ethanol-containing
> beverage, when writing documentation?
>

Nope, you're not alone. Certain activities do require lots of stimuli
(also known as "Gourmet Stout" :)

-- 
larsh

^ permalink raw reply

* Re: [PATCH] Silence error messages unless 'thorough_verify' is set
From: Johannes Schindelin @ 2007-06-10 10:08 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johan Herland, git
In-Reply-To: <7vwsyc8bt3.fsf@assigned-by-dhcp.cox.net>

Hi,

On Sun, 10 Jun 2007, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > ...  Guess how surprised 
> > _I_ was, when I hit the error message which made me go mad.
> 
> To be fair, that ugly "char%d" was taken from mktag and not
> Johan's invention.

Yes, I should have said that. I tried to hint to this by "you could just 
as well clean the code up", meaning the existing code.

Now, _that_ would be a patch I'd be really thankful for.

As for the general direction of implementing notes as tags: If you want to 
make them fetchable, you have to deal with conflicts. If you want to be 
able to amend notes, _especially_ when they should be fetchable, you want 
a history on them.

Which makes me think that tags are not the right object type for notes.

But I guess I'll just wait if somebody actually comments on my RFC for 
lightweight commit annotations (that's what I put into that discussion). 
BTW I just realized that I marked it [PATCH], while it should have been 
[RFC]. Sorry.

Ciao,
Dscho

^ permalink raw reply

* Re: [RFC] git integrated bugtracking
From: Pierre Habouzit @ 2007-06-10 10:16 UTC (permalink / raw)
  To: git, Martin Langhoff
In-Reply-To: <46a038f90706092359i43a6e834rc096e53a28fbee51@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 12981 bytes --]

On Sun, Jun 10, 2007 at 06:59:13PM +1200, Martin Langhoff wrote:
> On 6/10/07, Pierre Habouzit <madcoder@debian.org> wrote:
> >  FWIW I've begun to work on this (for real). I've called the tool
> >"grit". You can follow the developpement on:
> >
> >  * gitweb: http://git.madism.org/?p=grit.git;a=summary
> >  * git:    git://git.madism.org/grit.git/
> 
> Call me a fool, but writing a <new> bugtracker looks like a
> boil-the-oceans scheme.

  Sure, what if I like it anyway ?

> Adding git & gitweb support to traq, bugzilla, mantis, gforge, etc is
> what is going to make the difference. Most of those have already the
> ability to "link" to one or more commits -- after the commits are done
> and in GIT.

  Sure, you can do that and still inherit from the many downsides of
those tools: central, needs another separate tool to work with, and a
tool that nowadays tends to eat 500Mb of your memory if you take the
most hype implementation of it (Yes I'm describing a web browser), that
is slow like hell (because yes, there is many clients at the same time
on the central server, that is overloaded like hell), and so on.

  You can like central web UIs, your choice. And I suppose if grit works
well, there will be one at some point.

> So you can tell your bugtracker
> - which commit fixed it -- usually auto-linked if you include the
> bugnumber in the commit message
> - which commit added the test -- auto linked as above
> - which commit introduced the bug -- if such thing exists and someone
> digs it up

  yeah, that is what bugtrackers already do. Though, that's of no use
for release managers. What is useful is what people call "milestones" in
trac e.g., meaning a set of bugs that has been flagged as needing to be
fixed. And what a release manager need is a tool to deal with this set
of bugs as a whole.

  That's the same argument that Linus has against per-file tracking.
Also atm when you e.g. backport a patch that fixes this or this bug,
you're no BTS helps you tagging the bug as fixed in that branch as well.
Not to mention that BTS I know do not make any use of the commits DAG.
And for a very good reason, there is no real DAG in many existing tools
(svn, cvs, ...).

> If the bugtracker can also auto-link things that look committish in
> text entered by users (someone might write "bisect sez that f345e is
> to blame"), with tooltips indicating in which heads those commits
> resides (like gitk does), then it's just gorgeous.

  that's not up to the BTS tool to do that, it's way to high level. It's
up to the importing filters/hooks that will parse the associated ML, and
that would translate that to useful low level BTS commands.


> And definitely, if you use git as an alibi to write a new bugtracker,
> don't use the "works only with git" as a feature. It should work with
> as many SCMs as possible.

  No it should not, because it can't. I want the distributed and
Bug status spanning-DAGS be a central feature. So that means that this
tool can only work on top of SCMs that support that. ttbomk git, hg,
_maybe_ bzr fit the description. I only know the former, but I really
plan to write the tool in a way that the underlying SCM does not matters
_too_ much. Maybe I'll fail. Honestly, I don't really care (yet ?).



> OTOH, that's just me, I'm lazy and like to work on already-successful
> projects that are 99% there for my needs (and where I can add that
> 1%).

  You're a lucky guy. All bug trackers I've used suck a way or another,
that impedes my workflow a lot. Let's cite a couple:
  * bugzilla: takes more from the -zilla than from the BTS side. It's
    huge, monstruous, slow (have ever used glibc's bugzilla ? it has
    maybe 5k bugs, it's slow like if it ran on an Atari ST), complex,
    the mail gateway suck hard, it's completely unusable for me. Believe
    me, I've packaged KDE for 2 years in Debian, now am in the glibc
    team. Every single day I have to work on this horrible tool is a
    PITA.

  * flyspray: I've been upstream for a short time. Visually nice, good
    to work with, UI is great. Integration totally suck, worthless.
    Can't use mails either, needs a Web Browser -> useless. The same
    holds for mantis, roundup and a lot of other friends.

  * debbugs: oh yeah there is a mail interface. So slow that you have to
    wait up to 15 minutes to see your command be taken into account. And
    when you have to deal with dozens of bugs (Yes I've done that on a
    regular basis) you _have_ sometimes to wait for the answer to come
    back (because you need an ID that will be in there e.g.) to continue
    your work. That is unacceptable, you pass most of your time waiting.
    Moreover sometimes you made an error in your commands, so you also
    need to parse the anwer ... one day later because "immediateley when
    you still remembered what this was about" is not an option.
    Unacceptable again. The plus: it uses mboxes, hence is worthwile in
    a hacking environment, it fits the workflow well.

  * trac: very very very nice tool. I mean it. We use it at work, where
    I have to suffer svn (through git-svn though). It's really nice (did
    I repeat myself ?). THough, it's on top of svn, and you can't use
    the Bugs informations from your repository. You can't say: I'm
    backporting that patch into that branch. Now what affects this
    branch please ? Trac can't answer that (and ttbomk now BTS really
    can anyway, except Debian's debbugs instance, and it's somehow
    limited). That is a question a release manager takes 80% of his time
    to ask. I hope grit can take back to the 0.01% of his time, which is
    still too much.


On Sun, Jun 10, 2007 at 08:55:21PM +1200, Martin Langhoff wrote:
> On 6/10/07, Jan Hudec <bulb@ucw.cz> wrote:
> >I don't know about any *distributed* bug tracker, which is the point 
> >here.
> 
> As an end user, I suspect I _don't_ want to have to report a bug in a
> distributed bug tracker ;-)

  I trol^Wdiscuss everyday on debian's channel with friends that tell
that svn is the best tool ever, and that they would never ever use a
distributed SCM because it's too hard to understand. Your call.

> > We have several distributed version control tools, but no other
> > distributed tools for the other tasks in configuration management.
>
> Bugtrackers are co-owned by developers, users and (where they exist)
> project managers.

  That's exactly why distributed rock. Because everyone could _really_
own _his_ bugtracker. This solves the same range of ACLish issues git
solves with respect to the code.

> > - Distributed version control is designed to decrease the workload of
> >   the central maintainer(s) while keeping him in control. But with
> >   centralized
>
> And to provide a single place for users to report a problem and track
> its status.

  Why wouldn't it exist a "public reporting interface"-branch ? that
would be the same purpose as the mainline ~linus/linux2.6.git tree. And
you can build/instanciate your beloved web UI on top of that, and people
would just have to pull from there. What a shock, it's easy !

> > If it uses git as it's database, which it probably will,
>
> Well - hmmm. Git's database is great at tracking _content identity_.
> But a bug's content identity (description+comments+status) changes all
> the time. I don't think it's naturally a good match.

  Oh, because the code never changes. Doh, how stupid I am :)
  No, really, you name your bug after the sha hash of the first report,
I think that's pretty obvious. That gives you a bug name. Then you ask
git for "what's the current sha for this bug in the tip of the BTS
branch", then you ask "so now what this new sha is pointing to in the
code". That's a small indirection that I suppose is bearable.

> > Yes. But for many people current bug tracking tools do NOT work 99%.
>
> Hmmm. I agree in that "does not work disconnected" is a big issue with
> web tools, but debbugs works disconnected, and is good. Git's
> bugtracker (git@vger) works disconnected too ;-) And googlegears might
> help the rest of us. Is there any other problem with current BTs?

  It's not integrated with the workflow. And sorry, but git@vger (or
workse lkml@vger).. it does not work. Maybe for git it does because the
flow is still human manageable, and that it seems junio has enough time
for it. But for the kernel ? please. You should read Bastian Blank
frustration about regressions and nobody tracking them. Know why ?
because there is no tool that is well known and well integrated in the
workflow. There is a long rant against kernel.org's bugzilla, you should
read it as well. It's not really instructive (at least there wasn't
anything new for me in it, I was already bought to many arguments in
there). But you'll see the world isn't as rosa-lila you think it is.


On Sun, Jun 10, 2007 at 08:38:03PM +1200, Martin Langhoff wrote:
> On 6/10/07, Junio C Hamano <gitster@pobox.com> wrote:
> > After looking at the above existing alternatives, some brave soul
> > might decide and say, "Hey, I can write something better in 2 weeks"
> > ;-).

  I'm sure I could come up with something really better in say a
month... If I hadn't paid work to do too :)

> And "it's closely integrated with git" can actually be a misfeature.
> Cool if it's what gets you going, but not enough for world domination
> ;-)

  It's a misfeature for you because you read it as "non portable".
That's a fair point. And like said, it may be extended to SCM's with the
same set of features I need to build it. But let's be honnest, I don't
care about a BTS that uses the smallest common set of features of SCM's.
Reading this list, you should know it's almost a void set. No, I think a
good BTS should make an excellent use of high level features of the SCM.
The real problem is, there is maybe 2 or 3 SCMs that have this set of
strong and good features. Too bad for the other, I can't work with them,
they suck hard, and I don't see why I should support bad practices
anyways.

  (Yes I'm also a guy with strong opinions too, it's not really
restricted to Linus ;p)

> I agree it's useful, but I don't think it has any benefit having it in
> the SCM _at all_. Having them in the BT is a lot more flexible -- and
> the fact that git has stable commit IDs makes it easier to integrate
> in a BT; as the BT can spot that the commit fixing bug 123 is now
> merged into head ZZ as well as head YYY.

  If you do that you loose:
  * fastness, and I don't want to work with debbugs anymore.

  * distribution: Meaning that for _one_ project everybody needs to use
    this central bugtracker. Whereas .oO(kernel) there is some projects
    where the subcomponents are dealt with from very different teams,
    very different way to release things, and they are interested with
    their bugs, and their bugs only. They would prefer a very fast
    interface to deal with their 1k bugs, and not worry about the 100k
    the rest of the project has.
    Branching bugs also makes sense you know ?

> Now, to rule the world, BTs gain a lot more from being able to
> integrate with different SCMs,

  You are the one saying it. I beg to differ.

> automated test systems (like tinderbox), MTAs (debbugs), wikis (traq),
> stats tracking for PHBs (bugzilla), etc. So loose coupling wins here,
> and git's SHA1s are great for that.

  IMHO a BTS is a _low_ level tool. that's the road git took, I
sometimes describe the plumbing git commands as the "Assembler" of the
SCM world to friends when I talk to them about git. That's really the
best way to implement a thing: have a small small set of rock solid,
well designed tools, and build the others as porcelains with them.

  Testing is a high level tool. I don't need to support them, I need to
have exactly the low level querying rocket-fast query interfaces so that
integration scripts are at most 100 lines long.

> And at git's end we can get the smooth integration using/abusing that
> loose coupling strategy. So if git-log/gitk/qgit grow some hooks that
> can be used to perform a lookup... I can think of a perl script that
> gets events for each SHA1 that git-rev-list produces and returns one
> bug number, url and title (or more than one) that then git-log / gitk
> / qgit / annotate can use to further "decorate" the commit info in a
> similar style to what gitk is doing now with head names in each
> commit.
> 
> Would that fit your vision of a nicely integrated tracker?

  Honestly ? No, because that would be horribly slow (but I'd love to be
proven wrong).


Cheers,
-- 
·O·  Pierre Habouzit
··O                                                madcoder@debian.org
OOO                                                http://www.madism.org

[-- Attachment #2: Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply

* Re: [RFC] git integrated bugtracking
From: Jan Hudec @ 2007-06-10 10:49 UTC (permalink / raw)
  To: Martin Langhoff; +Cc: git
In-Reply-To: <46a038f90706100155q1da663d7le3bf0345c68e47ae@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 5972 bytes --]

On Sun, Jun 10, 2007 at 20:55:21 +1200, Martin Langhoff wrote:
> On 6/10/07, Jan Hudec <bulb@ucw.cz> wrote:
> >I don't know about any *distributed* bug tracker, which is the point here.
> 
> As an end user, I suspect I _don't_ want to have to report a bug in a
> distributed bug tracker ;-)

As an end user, you would just post the bug report to a mailing list. The bug
tracker would take care of adding it to the "master repository".

> In that space, the Malone guys (canonical)
> have their fingers on one of the most serious issues, and perhaps it's
> interesting to see what they've done there. It's really useful, even
> if I don't think I want to have to maintain it :-/

I don't think they even have a mail interface. You have to register to post
a bug, just as with most of the crappy web-based bug trackers out there.

They have some nice integration with bazaar, but AFAIK the branch has to be
mirrored on launchpad, to which the whole thing is really tied too much.
Also I can find download link anywhere there, nor anywhere they'd state it's
license (and from what I heard it is NOT open-source).

So while they might have some nice features, it's not that suitable for
general public.

> >We have several distributed version control tools, but no other distributed
> >tools for the other tasks in configuration management.
> 
> Bugtrackers are co-owned by developers, users and (where they exist)
> project managers.

I agree that branches don't make that much sense in bug trackers. Except in
rare cases like when the project is forked or such. The bug tracker is there
so the people stay in sync. However I think trying to design a distributed
bug tracker can bring useful insignts into the problems involved.

> > - The web interface is usually not a good match for the problem. Email
> >   interface is better in many respects, but it still does not cut it.
> 
> I agree. I love/hate debbugs too.

... there's a related issue of what the mail user agents can do. If my mail
client was really good as personal task manager, mail-based bug trackers
would work quite well. But the MUAs are not all that good.

> > - You can't really use the ability of version control to work 
> > disconnected,
> >   when you don't also have the bug information.
> 
> A cache fixes the reading part  - see my other post, and imagine being
> able to have a local sqlite cache of the BT key data indexed by
> referenced SHA1, showing up with your commits in gitk.
> 
> The write part is solved (in part) by committing to git the fix -- if
> you mentionm the bug ID, the central BT will pick it up when your
> commit appears in the branches/repos that the BT can see.
> 
> For "just adding a comment", the write part is solved by the "email"
> interface, like with debbugs.
> 
> > - Distributed version control is designed to decrease the workload of the
> >   central maintainer(s) while keeping him in control. But with centralized
> 
> And to provide a single place for users to report a problem and track
> its status.

Just like there is the "master" repository, there would be the "master" bug
tracker.

> >If it uses git as it's database, which it probably will,
> 
> Well - hmmm. Git's database is great at tracking _content identity_.
> But a bug's content identity (description+comments+status) changes all
> the time. I don't think it's naturally a good match.
> 
> Perhaps it makes sense to mix git's storage model with something else...?

You are right here. Git can be used to store the data bits, but they need to
be glued together somehow (with tags or something). So we can as well store
them in some other kind of database and it might be even better.

> >Yes. But for many people current bug tracking tools do NOT work 99%.
> 
> Hmmm. I agree in that "does not work disconnected" is a big issue with
> web tools, but debbugs works disconnected, and is good. Git's
> bugtracker (git@vger) works disconnected too ;-) And googlegears might
> help the rest of us. Is there any other problem with current BTs?

Well, there is no BT that would satisfy all the points above ;-). Debbugs has
a good email interface, but it's a huge beast and tied with the Debian
archive logic quite a lot. The rest -- except maybe gnats (I'll have to look
at that -- it looks interesting) -- does not seem to have much sensible email
support.

Following up on the note about MUAs above, one interesting idea to create
a bug tracker (that would not be git specific nor actually use git for
anything) would be to:
 - Create a mailing list bot, that would watch for bug reports either on
   dedicated address or by watching for [BUG] or something, and replies to
   them.
 - Export the bug database via read-only imap (or pop3 or webdav). There are
   already tools to create local cache for such protocols, which would
   resolve the disconnected issue and give good query speed, because the data
   would be local.
 - The server would make sure that all the messages applying to a particular
   issue are correctly linked with In-Reply-To: headers to form a single
   thread.
 - Status of the bug could be expressed with mailbox it is in (backwards
   compatible way) or some kind of tags (which would need to be supported by
   the mail client -- I really think it's about time for something like
   that).
 - Other metainformation about the bug could be added as a special message
   inserted at the begining of the thread, or added into the first message
 - Than support for todo management could be improved in some mail clients.
   It is not precondition for this kind of bug tracking to be useful and on
   the other hand would be useful on it's own.
 - It could even be done as another interface to an existing web-based BT,
   though I would prefer if the web interface would not have to be running
   then.

-- 
						 Jan 'Bulb' Hudec <bulb@ucw.cz>

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply

* Re: StGIT: stgitformatversion vs. stgit.formatversion
From: Karl Hasselström @ 2007-06-10 11:01 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: Yann Dirson, GIT list
In-Reply-To: <b0943d9e0706071455n2136c75fm76571583c279d23b@mail.gmail.com>

On 2007-06-07 22:55:09 +0100, Catalin Marinas wrote:

> BTW, if any of you is willing to maintain such a branch, I'll
> happily pull from it when it gets stable.

I'm on vacation the next few weeks (though this week I'm at the FCRC
in San Diego if someone wants to say hi!) and have rather spotty
internet connectivity, so I haven't really been able to follow the
mailing list. But I think this would be useful, so I'll probably start
doing it as soon as I'm all settled again.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply

* [ANNOUNCE] qgit-2.0rc1
From: Marco Costalba @ 2007-06-10 11:47 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Pavel Roskin, Andy Parkins

This is qgit-2.0rc1

With qgit you will be able to browse revision histories, view patch
content and changed files, graphically following different development
branches.


FEATURES

 - View revisions, diffs, files history, files annotation, archive tree.

 - Commit changes visually cherry picking modified files.

  - Apply or format patch series from selected commits, drag and drop
commits between two instances of qgit.

 - Associate commands sequences, scripts and anything else executable
to a custom action. Actions can be run from menu and corresponding
output is grabbed by a terminal window.

  - qgit implements a GUI for the most common StGIT commands like
push/pop and apply/format patches. You can also create new patches or
refresh current top one using the same semantics of git commit, i.e.
cherry picking single modified files.


NEW IN THIS RELEASE

This is the first public release of the shining new qgit 2.0 based on
the shining new Qt 4.2 libraries.

This is the result of more the five months of porting efforts.

All the features of the stable series have been ported. In addition
this new release sports a better GUI both on visually side, thanks to
the work of Andy Parkins on the new revision description pane header,
and on usability side.

A new and much improved repository browsing experience is now
possible, in particular you don't need to switch to patch tab to view
diff content anymore. Patch information, together with patch
description, is shown in bottom left pane.

New browsing features are:

- Possibility to switch from revision message to diff content in
  bottom left pane of main view (from menu or key shortcut 'm')

- Possibility to jump to next/previous revision with link labels at
  the pane corners

- Optional use of mouse wheel to perform all of the above actions

- Added small tabs to description pane with log and diff info.


All this is tweakable from 'Edit->Settings->Browse' menu dialog, so to
let fine grain control on the preferred browse setting.

For an history viewer easy and quick browsing is the single MOST
important feature.


A lot has changed, apart from browsing, the list would be very long,
so the best thing is to download and check yourself.

This is a 'rc' release, but should be already quite stable, so I
encourage everyone interested to give it a spin.


You can download the tarball from
http://downloads.sourceforge.net/qgit/qgit-2.0rc1.tar.bz2

or clone the public git archive at
git://git.kernel.org/pub/scm/qgit/qgit4.git


Please, check the shipped README for detailed build and install information.


I would like to thank Pavel Roskin and Andy Parkins for their very
valuable support in shaping this new release.


Ah, the last thing, because we are Qt4 now, qgit 2.0rc1 works natively
also under Windows.


Have fun
Marco

^ permalink raw reply

* [PATCH 0/4] Restructure the tag object
From: Johan Herland @ 2007-06-10 11:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Schindelin, git
In-Reply-To: <7vwsyc8bt3.fsf@assigned-by-dhcp.cox.net>

Ok, I'm pulling the 21-part patch series from hell. It's just not worth all
the flak. Here's a 4-part patch series that tries to do the changes needed
without all the crap^Wrefactoring.

Obviously this patch series does none of the much needed cleanup in this
part of the code (e.g. better error messages, specifying encodings of header
fields, possibly unifying the common parts between the parser and the
verifier). I'll leave that cleanup to someone who writes less crappy code.

Here's the shortlog for the series:

Johan Herland (4):
      Make tag names (i.e. the tag object's "tag" line) optional
      Introduce optional "keywords" on tag objects
      Documentation/git-mktag: Document the changes in tag object structure
      git-mktag tests: Expand on mktag selftests according to the new tag object structure

 Documentation/git-mktag.txt |   38 +++++++---
 mktag.c                     |   65 +++++++++++-----
 t/t3800-mktag.sh            |  172 ++++++++++++++++++++++++++++++++++++++++---
 tag.c                       |   44 +++++++++--
 tag.h                       |    3 +-
 5 files changed, 270 insertions(+), 52 deletions(-)


...Johan

^ permalink raw reply

* [PATCH 1/4] Make tag names (i.e. the tag object's "tag" line) optional
From: Johan Herland @ 2007-06-10 11:49 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin
In-Reply-To: <200706101347.57023.johan@herland.net>

The tag line is now optional. If not given in the tag object data, it
defaults to the empty string ("") in the parsed tag object.

Also includes selftest tweaks to make them work with optional tag names.

Signed-off-by: Johan Herland <johan@herland.net>
---
 mktag.c          |   37 ++++++++++++++++++++-----------------
 t/t3800-mktag.sh |   16 +++++++++-------
 tag.c            |   18 ++++++++++++------
 tag.h            |    2 +-
 4 files changed, 42 insertions(+), 31 deletions(-)

diff --git a/mktag.c b/mktag.c
index 070bc96..5e80d3d 100644
--- a/mktag.c
+++ b/mktag.c
@@ -2,16 +2,15 @@
 #include "tag.h"
 
 /*
- * A signature file has a very simple fixed format: four lines
- * of "object <sha1>" + "type <typename>" + "tag <tagname>" +
+ * A signature file has a very simple format: 3-4 lines
+ * of "object <sha1>" + "type <typename>" + "tag <tagname>" (optional) +
  * "tagger <committer>", followed by a blank line, a free-form tag
  * message and a signature block that git itself doesn't care about,
  * but that can be verified with gpg or similar.
  *
- * The first three lines are guaranteed to be at least 63 bytes:
- * "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the
- * shortest possible type-line, and "tag .\n" at 6 bytes is the
- * shortest single-character-tag line.
+ * The first two lines are guaranteed to be at least 57 bytes:
+ * "object <sha1>\n" is 48 bytes and "type tag\n" at 9 bytes is the
+ * shortest possible type-line.
  *
  * We also artificially limit the size of the full object to 8kB.
  * Just because I'm a lazy bastard, and if you can't fit a signature
@@ -52,7 +51,7 @@ static int verify_tag(char *buffer, unsigned long size)
 	unsigned char sha1[20];
 	const char *object, *type_line, *tag_line, *tagger_line;
 
-	if (size < 64)
+	if (size < 58)
 		return error("wanna fool me ? you obviously got the size wrong !");
 
 	buffer[size] = 0;
@@ -75,8 +74,6 @@ static int verify_tag(char *buffer, unsigned long size)
 	if (!tag_line)
 		return error("char" PD_FMT ": could not find next \"\\n\"", type_line - buffer);
 	tag_line++;
-	if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n')
-		return error("char" PD_FMT ": no \"tag \" found", tag_line - buffer);
 
 	/* Get the actual type */
 	typelen = tag_line - type_line - strlen("type \n");
@@ -91,14 +88,20 @@ static int verify_tag(char *buffer, unsigned long size)
 		return error("char%d: could not verify object %s", 7, sha1_to_hex(sha1));
 
 	/* Verify the tag-name: we don't allow control characters or spaces in it */
-	tag_line += 4;
-	for (;;) {
-		unsigned char c = *tag_line++;
-		if (c == '\n')
-			break;
-		if (c > ' ')
-			continue;
-		return error("char" PD_FMT ": could not verify tag name", tag_line - buffer);
+	if (!memcmp(tag_line, "tag ", 4)) {
+		if (tag_line[4] == '\n')
+			return error("char" PD_FMT ": no \"tag \" found",
+					tag_line - buffer);
+		tag_line += 4;
+		for (;;) {
+			unsigned char c = *tag_line++;
+			if (c == '\n')
+				break;
+			if (c > ' ')
+				continue;
+			return error("char" PD_FMT ": could not verify tag name",
+					tag_line - buffer);
+		}
 	}
 
 	/* Verify the tagger line */
diff --git a/t/t3800-mktag.sh b/t/t3800-mktag.sh
index 7c7e433..ca90662 100755
--- a/t/t3800-mktag.sh
+++ b/t/t3800-mktag.sh
@@ -100,13 +100,14 @@ check_verify_failure '"type" line eol check'
 #  6. tag line label check #1
 
 cat >tag.sig <<EOF
-object 779e9b33986b1c2670fff52c5067603117b3e895
-type tag
+object $head
+type commit
 xxx mytag
+tagger a
 EOF
 
 cat >expect.pat <<EOF
-^error: char57: no "tag " found$
+^error: char60: could not find "tagger"$
 EOF
 
 check_verify_failure '"tag" line label check #1'
@@ -115,13 +116,14 @@ check_verify_failure '"tag" line label check #1'
 #  7. tag line label check #2
 
 cat >tag.sig <<EOF
-object 779e9b33986b1c2670fff52c5067603117b3e895
-type taggggggggggggggggggggggggggggggg
+object $head
+type commit
 tag
+tagger a
 EOF
 
 cat >expect.pat <<EOF
-^error: char87: no "tag " found$
+^error: char60: could not find "tagger"$
 EOF
 
 check_verify_failure '"tag" line label check #2'
@@ -130,7 +132,7 @@ check_verify_failure '"tag" line label check #2'
 #  8. type line type-name length check
 
 cat >tag.sig <<EOF
-object 779e9b33986b1c2670fff52c5067603117b3e895
+object $head
 type taggggggggggggggggggggggggggggggg
 tag mytag
 EOF
diff --git a/tag.c b/tag.c
index bbacd59..a7a3454 100644
--- a/tag.c
+++ b/tag.c
@@ -44,7 +44,7 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
                 return 0;
         item->object.parsed = 1;
 
-	if (size < 64)
+	if (size < 58)
 		return -1;
 	if (memcmp("object ", data, 7) || get_sha1_hex((char *) data + 7, sha1))
 		return -1;
@@ -54,13 +54,17 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
 		return -1;
 
 	tag_line = strchr(type_line, '\n');
-	if (!tag_line || memcmp("tag ", ++tag_line, 4))
+	if (!tag_line)
 		return -1;
 
-	sig_line = strchr(tag_line, '\n');
-	if (!sig_line)
-		return -1;
-	sig_line++;
+	if (!memcmp("tag ", ++tag_line, 4)) {
+		sig_line = strchr(tag_line, '\n');
+		if (!sig_line)
+			return -1;
+		sig_line++;
+	}
+	else
+		sig_line = tag_line;
 
 	typelen = tag_line - type_line - strlen("type \n");
 	if (typelen >= 20)
@@ -68,6 +72,8 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
 	memcpy(type, type_line + 5, typelen);
 	type[typelen] = '\0';
 	taglen = sig_line - tag_line - strlen("tag \n");
+	if (taglen < 0) /* missing tag name */
+		taglen = 0;
 	item->tag = xmalloc(taglen + 1);
 	memcpy(item->tag, tag_line + 4, taglen);
 	item->tag[taglen] = '\0';
diff --git a/tag.h b/tag.h
index 7a0cb00..7e0abbe 100644
--- a/tag.h
+++ b/tag.h
@@ -8,7 +8,7 @@ extern const char *tag_type;
 struct tag {
 	struct object object;
 	struct object *tagged;
-	char *tag;
+	char *tag;       /* optional, may be empty ("") */
 	char *signature; /* not actually implemented */
 };
 
-- 
1.5.2.1.144.gabc40

^ permalink raw reply related

* [PATCH 2/4] Introduce optional "keywords" on tag objects
From: Johan Herland @ 2007-06-10 11:50 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin
In-Reply-To: <200706101347.57023.johan@herland.net>

This patch introduces a new optional header line to the tag object, called
"keywords". The "keywords" line may contain a comma-separated list of
custom keywords associated with the tag object. There are two "special"
keywords, however: "tag" and "note": When the "keywords" header is
missing, its default value is set to "tag" if a "tag" header is
present; else the default "keywords" value is set to "note". The
"keywords" header is meant to be used by porcelains for classifying
different types of tag objects. This classification may then be used to
filter tag objects in the presentation layer (e.g. by implementing
extra filter options to --decorate, etc.).

The encoding rules for keywords are identical to those of tag names.

Signed-off-by: Johan Herland <johan@herland.net>
---
 mktag.c |   30 ++++++++++++++++++++++++++----
 tag.c   |   30 +++++++++++++++++++++++++-----
 tag.h   |    1 +
 3 files changed, 52 insertions(+), 9 deletions(-)

diff --git a/mktag.c b/mktag.c
index 5e80d3d..37e10c6 100644
--- a/mktag.c
+++ b/mktag.c
@@ -2,9 +2,10 @@
 #include "tag.h"
 
 /*
- * A signature file has a very simple format: 3-4 lines
+ * A signature file has a very simple format: 3-5 lines
  * of "object <sha1>" + "type <typename>" + "tag <tagname>" (optional) +
- * "tagger <committer>", followed by a blank line, a free-form tag
+ * "keywords <keywords>" (optional) + "tagger <committer>",
+ * followed by a blank line, a free-form tag
  * message and a signature block that git itself doesn't care about,
  * but that can be verified with gpg or similar.
  *
@@ -49,7 +50,7 @@ static int verify_tag(char *buffer, unsigned long size)
 	int typelen;
 	char type[20];
 	unsigned char sha1[20];
-	const char *object, *type_line, *tag_line, *tagger_line;
+	const char *object, *type_line, *tag_line, *keywords_line, *tagger_line;
 
 	if (size < 58)
 		return error("wanna fool me ? you obviously got the size wrong !");
@@ -104,8 +105,29 @@ static int verify_tag(char *buffer, unsigned long size)
 		}
 	}
 
+	/* Verify the keywords: disallow ctrl chars, spaces and double commas */
+	keywords_line = tag_line;
+
+	if (!memcmp(tag_line, "keywords ", 9)) {
+		if (tag_line[9] == '\n')
+			return error("char" PD_FMT ": no \"keywords \" found",
+					keywords_line - buffer);
+		keywords_line += 9;
+		for (;;) {
+			unsigned char c = *keywords_line++;
+			if (c == '\n')
+				break;
+			if (c == ',' && *keywords_line == ',')
+				/* double commas. fall through to error() */;
+			else if (c > ' ')
+				continue;
+			return error("char" PD_FMT ": could not verify keywords",
+					keywords_line - buffer);
+		}
+	}
+
 	/* Verify the tagger line */
-	tagger_line = tag_line;
+	tagger_line = keywords_line;
 
 	if (memcmp(tagger_line, "tagger", 6) || (tagger_line[6] == '\n'))
 		return error("char" PD_FMT ": could not find \"tagger\"", tagger_line - buffer);
diff --git a/tag.c b/tag.c
index a7a3454..b74f09f 100644
--- a/tag.c
+++ b/tag.c
@@ -35,9 +35,9 @@ struct tag *lookup_tag(const unsigned char *sha1)
 
 int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
 {
-	int typelen, taglen;
+	int typelen, taglen, keywordslen;
 	unsigned char sha1[20];
-	const char *type_line, *tag_line, *sig_line;
+	const char *type_line, *tag_line, *keywords_line, *sig_line;
 	char type[20];
 
         if (item->object.parsed)
@@ -58,25 +58,45 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
 		return -1;
 
 	if (!memcmp("tag ", ++tag_line, 4)) {
-		sig_line = strchr(tag_line, '\n');
+		keywords_line = strchr(tag_line, '\n');
+		if (!keywords_line)
+			return -1;
+		keywords_line++;
+	}
+	else
+		keywords_line = tag_line;
+
+	if (!memcmp("keywords ", keywords_line, 9)) {
+		sig_line = strchr(keywords_line, '\n');
 		if (!sig_line)
 			return -1;
 		sig_line++;
 	}
 	else
-		sig_line = tag_line;
+		sig_line = keywords_line;
 
 	typelen = tag_line - type_line - strlen("type \n");
 	if (typelen >= 20)
 		return -1;
 	memcpy(type, type_line + 5, typelen);
 	type[typelen] = '\0';
-	taglen = sig_line - tag_line - strlen("tag \n");
+	taglen = keywords_line - tag_line - strlen("tag \n");
 	if (taglen < 0) /* missing tag name */
 		taglen = 0;
 	item->tag = xmalloc(taglen + 1);
 	memcpy(item->tag, tag_line + 4, taglen);
 	item->tag[taglen] = '\0';
+	keywordslen = sig_line - keywords_line - strlen("keywords \n");
+	if (keywordslen > 0)
+		keywords_line += strlen("keywords ");
+	else { /* missing keywords */
+		if (taglen) /* tag name given */
+			keywords_line = "tag";
+		else
+			keywords_line = "note";
+		keywordslen = strlen(keywords_line);
+	}
+	item->keywords = xstrndup(keywords_line, keywordslen);
 
 	if (!strcmp(type, blob_type)) {
 		item->tagged = &lookup_blob(sha1)->object;
diff --git a/tag.h b/tag.h
index 7e0abbe..6e687b2 100644
--- a/tag.h
+++ b/tag.h
@@ -9,6 +9,7 @@ struct tag {
 	struct object object;
 	struct object *tagged;
 	char *tag;       /* optional, may be empty ("") */
+	char *keywords;  /* optional, defaults to (tag ? "tag" : "note") */
 	char *signature; /* not actually implemented */
 };
 
-- 
1.5.2.1.144.gabc40

^ permalink raw reply related

* [PATCH 3/4] Documentation/git-mktag: Document the changes in tag object structure
From: Johan Herland @ 2007-06-10 11:50 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin
In-Reply-To: <200706101347.57023.johan@herland.net>

The new structure of tag objects is documented.

Also some much-needed cleanup is done. E.g. remove the paragraph on the
8kB limit, since this limit was removed ages ago.

Signed-off-by: Johan Herland <johan@herland.net>
---
 Documentation/git-mktag.txt |   38 +++++++++++++++++++++++++++-----------
 1 files changed, 27 insertions(+), 11 deletions(-)

diff --git a/Documentation/git-mktag.txt b/Documentation/git-mktag.txt
index 0ac3be1..e6dfed6 100644
--- a/Documentation/git-mktag.txt
+++ b/Documentation/git-mktag.txt
@@ -8,29 +8,44 @@ git-mktag - Creates a tag object
 
 SYNOPSIS
 --------
-'git-mktag' < signature_file
+[verse]
+'git-mktag' < tag_data_file
 
 DESCRIPTION
 -----------
-Reads a tag contents on standard input and creates a tag object
+Reads tag object data on standard input and creates a tag object
 that can also be used to sign other objects.
 
 The output is the new tag's <object> identifier.
 
-Tag Format
+DISCUSSION
 ----------
-A tag signature file has a very simple fixed format: three lines of
+Tag object data has the following format
 
+[verse]
   object <sha1>
   type <typename>
-  tag <tagname>
+  tag <tagname>               (optional)
+  keywords <keywords>         (optional)
+  tagger <committer>
 
-followed by some 'optional' free-form signature that git itself
-doesn't care about, but that can be verified with gpg or similar.
+followed by a blank line and a free-form message and an optional
+signature that git itself doesn't care about, but that may be
+verified with gpg or similar.
 
-The size of the full object is artificially limited to 8kB.  (Just
-because I'm a lazy bastard, and if you can't fit a signature in that
-size, you're doing something wrong)
+In the above listing, `<sha1>` represents the object pointed to
+by this tag, `<typename>` is the type of the object pointed to
+("tag", "blob", "tree" or "commit"), `<tagname>` is the name of
+this tag object (and must correspond to the name of the corresponding
+ref (if any) in `.git/refs/`). `<keywords>` is a comma-separated
+list of keywords associated with this tag object, and `<committer>`
+holds the "`name <email>`" of the tag creator and timestamp of when
+the tag object was created (analogous to "committer" in commit
+objects).
+
+If "`tag <tagname>`" is omitted, <tagname> defaults to the empty
+string. If "`keywords <keywords>`" is omitted, <keywords> defaults
+to "`tag`" if a <tagname> was given, "`note`" otherwise.
 
 
 Author
@@ -39,7 +54,8 @@ Written by Linus Torvalds <torvalds@osdl.org>
 
 Documentation
 --------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+Documentation by Johan Herland, David Greaves, Junio C Hamano
+and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-- 
1.5.2.1.144.gabc40

^ permalink raw reply related

* [PATCH 4/4] git-mktag tests: Expand on mktag selftests according to the new tag object structure
From: Johan Herland @ 2007-06-10 11:50 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin
In-Reply-To: <200706101347.57023.johan@herland.net>

Some more tests are added to test the new "keywords" header.

Signed-off-by: Johan Herland <johan@herland.net>
---
 t/t3800-mktag.sh |  156 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 152 insertions(+), 4 deletions(-)

diff --git a/t/t3800-mktag.sh b/t/t3800-mktag.sh
index ca90662..cc2f246 100755
--- a/t/t3800-mktag.sh
+++ b/t/t3800-mktag.sh
@@ -174,7 +174,95 @@ EOF
 check_verify_failure 'verify tag-name check'
 
 ############################################################
-# 11. tagger line label check #1
+# 11. keywords line label check #1
+
+cat >tag.sig <<EOF
+object $head
+type commit
+tag mytag
+xxxxxxxx foo
+EOF
+
+cat >expect.pat <<EOF
+^error: char70: could not find "tagger"$
+EOF
+
+check_verify_failure '"keywords" line label check #1'
+
+############################################################
+# 12. keywords line label check #2
+
+cat >tag.sig <<EOF
+object $head
+type commit
+tag mytag
+keywords
+tagger bar@baz.com
+
+EOF
+
+cat >expect.pat <<EOF
+^error: char70: could not find "tagger"$
+EOF
+
+check_verify_failure '"keywords" line label check #2'
+
+############################################################
+# 13. keywords line check #1
+
+cat >tag.sig <<EOF
+object $head
+type commit
+tag mytag
+keywords foo bar	baz
+tagger bar@baz.com
+
+EOF
+
+cat >expect.pat <<EOF
+^error: char83: could not verify keywords$
+EOF
+
+check_verify_failure '"keywords" line check #1'
+
+############################################################
+# 14. keywords line check #2
+
+cat >tag.sig <<EOF
+object $head
+type commit
+tag mytag
+keywords foo,bar	baz
+tagger bar@baz.com
+
+EOF
+
+cat >expect.pat <<EOF
+^error: char87: could not verify keywords$
+EOF
+
+check_verify_failure '"keywords" line check #2'
+
+############################################################
+# 15. keywords line check #3
+
+cat >tag.sig <<EOF
+object $head
+type commit
+tag mytag
+keywords foo,,bar
+tagger bar@baz.com
+
+EOF
+
+cat >expect.pat <<EOF
+^error: char83: could not verify keywords$
+EOF
+
+check_verify_failure '"keywords" line check #3'
+
+############################################################
+# 16. tagger line label check #1
 
 cat >tag.sig <<EOF
 object $head
@@ -189,7 +277,7 @@ EOF
 check_verify_failure '"tagger" line label check #1'
 
 ############################################################
-# 12. tagger line label check #2
+# 17. tagger line label check #2
 
 cat >tag.sig <<EOF
 object $head
@@ -205,7 +293,7 @@ EOF
 check_verify_failure '"tagger" line label check #2'
 
 ############################################################
-# 13. create valid tag
+# 18. create valid tag #1
 
 cat >tag.sig <<EOF
 object $head
@@ -219,11 +307,71 @@ test_expect_success \
     'git-mktag <tag.sig >.git/refs/tags/mytag 2>message'
 
 ############################################################
-# 14. check mytag
+# 19. check mytag
 
 test_expect_success \
     'check mytag' \
     'git-tag -l | grep mytag'
 
+############################################################
+# 20. create valid tag #2
+
+cat >tag.sig <<EOF
+object $head
+type commit
+tagger another@example.com
+
+EOF
+
+test_expect_success \
+    'create valid tag #2' \
+    'git-mktag <tag.sig >.git/refs/tags/mytag 2>message'
+
+############################################################
+# 21. create valid tag #3
+
+cat >tag.sig <<EOF
+object $head
+type commit
+keywords foo,bar,baz,spam,spam,spam,spam,spam,spam,spam,spam
+tagger another@example.com
+
+EOF
+
+test_expect_success \
+    'create valid tag #3' \
+    'git-mktag <tag.sig >.git/refs/tags/mytag 2>message'
+
+############################################################
+# 22. create valid tag #4
+
+cat >tag.sig <<EOF
+object $head
+type commit
+tag mytag
+keywords note
+tagger another@example.com
+
+EOF
+
+test_expect_success \
+    'create valid tag #4' \
+    'git-mktag <tag.sig >.git/refs/tags/mytag 2>message'
+
+############################################################
+# 23. create valid tag #5 (with empty message)
+
+cat >tag.sig <<EOF
+object $head
+type commit
+tag mytag
+keywords note
+tagger a
+EOF
+
+test_expect_success \
+    'create valid tag #4' \
+    'git-mktag <tag.sig >.git/refs/tags/mytag 2>message'
+
 
 test_done
-- 
1.5.2.1.144.gabc40

^ permalink raw reply related

* Re: [PATCH] Silence error messages unless 'thorough_verify' is set
From: Johan Herland @ 2007-06-10 12:10 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Junio C Hamano
In-Reply-To: <Pine.LNX.4.64.0706101103160.4059@racer.site>

On Sunday 10 June 2007, Johannes Schindelin wrote:
> As for the general direction of implementing notes as tags: If you want to 
> make them fetchable, you have to deal with conflicts. If you want to be 
> able to amend notes, _especially_ when they should be fetchable, you want 
> a history on them.

I'm not sure what kind of notes you're talking about here. If you're talking 
about my git-note concept, I designed notes to be immutable (thus not 
amendable) and there is therefore _no_ merging or potential for conflicts 
between notes. The only resolution needed is to figure out which order the 
notes for a given object should be presented. The default here is 
chronological sorting.


...Johan

-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply

* Re: [PATCH 2/3] Add gitmodules(5)
From: Sven Verdoolaege @ 2007-06-10 12:12 UTC (permalink / raw)
  To: Lars Hjemli; +Cc: Frank Lichtenheld, Junio C Hamano, Johannes Schindelin, git
In-Reply-To: <8c5c35580706100158n7dabfce4y5f79f8943d8abb87@mail.gmail.com>

On Sun, Jun 10, 2007 at 10:58:29AM +0200, Lars Hjemli wrote:
> On 6/10/07, Frank Lichtenheld <frank@lichtenheld.de> wrote:
> >> +Each submodule can contain the following keys.
> >> +
> >> +module.$name.path::
> >> +     Define a path, relative to the top-level directory of the git
> >> +     working tree, where the submodule is expected to be checked out.
> >> +
> >> +module.$name.url::
> >> +     Define a url from where the submodule repository can be cloned.
> >
> >For .path a "Defaults to name of submodule" probably wouldn't hurt.
> 
> True. But there might be some issues with this rule, so I'll leave it
> as is for now.

And what might those issues be?

How about adding something like

	For a given path relative to the top-level directory of the
	git working tree, the logical name of the submodule at that
	path (if any) is the submodule <name> with a module.<name>.path
	value equal to that path, or, if no such submodule exists, the
	submodule <path>.  It is an error for different submodules to have
	identical path values.

For uniformity, you may also want to specify that a path value
must (or must not) end with a slash.

Other than that, I like it.
It doesn't have the extensions proposed by Linus, but it seems
to be forward compatible with them.

skimo

^ permalink raw reply

* Re: [ANNOUNCE] qgit-2.0rc1
From: Ismail Dönmez @ 2007-06-10 12:14 UTC (permalink / raw)
  To: Marco Costalba; +Cc: Git Mailing List, Pavel Roskin, Andy Parkins
In-Reply-To: <e5bfff550706100447g5d34adf2j92389cd193658738@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 503 bytes --]

Hi,
On Sunday 10 June 2007 14:47:17 Marco Costalba wrote:
> This is qgit-2.0rc1
>
> With qgit you will be able to browse revision histories, view patch
> content and changed files, graphically following different development
> branches.

Doesn't seem to compile here. make output is at 
http://cekirdek.pardus.org.tr/~ismail/tmp/qgit4-error.txt . Qt is built with 
no exceptions (as suggested by Trolltech), might that be the problem?

Regards,
ismail

-- 
Perfect is the enemy of good

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 827 bytes --]

^ permalink raw reply

* Re: [PATCH 2/3] Add gitmodules(5)
From: Lars Hjemli @ 2007-06-10 12:30 UTC (permalink / raw)
  To: skimo; +Cc: Frank Lichtenheld, Junio C Hamano, Johannes Schindelin, git
In-Reply-To: <20070610121202.GD955MdfPADPa@greensroom.kotnet.org>

On 6/10/07, Sven Verdoolaege <skimo@kotnet.org> wrote:
> On Sun, Jun 10, 2007 at 10:58:29AM +0200, Lars Hjemli wrote:
> > On 6/10/07, Frank Lichtenheld <frank@lichtenheld.de> wrote:
> > >> +Each submodule can contain the following keys.
> > >> +
> > >> +module.$name.path::
> > >> +     Define a path, relative to the top-level directory of the git
> > >> +     working tree, where the submodule is expected to be checked out.
> > >> +
> > >> +module.$name.url::
> > >> +     Define a url from where the submodule repository can be cloned.
> > >
> > >For .path a "Defaults to name of submodule" probably wouldn't hurt.
> >
> > True. But there might be some issues with this rule, so I'll leave it
> > as is for now.
>
> And what might those issues be?

There's been some discussion about allowing a default value for path, see
  http://comments.gmane.org/gmane.comp.version-control.git/49620

>
> How about adding something like
>
>         For a given path relative to the top-level directory of the
>         git working tree, the logical name of the submodule at that
>         path (if any) is the submodule <name> with a module.<name>.path
>         value equal to that path, or, if no such submodule exists, the
>         submodule <path>.  It is an error for different submodules to have
>         identical path values.
>
> For uniformity, you may also want to specify that a path value
> must (or must not) end with a slash.

Nice suggestions, I'll try to come up with a new patch later today

> Other than that, I like it.

Thanks :)

> It doesn't have the extensions proposed by Linus, but it seems
> to be forward compatible with them.

I'm trying to take baby-steps with the submodule support, so the
module/submodule extension Linus talked about would possibly come at a
later stage.

Hmm, maybe I should just rename [module] to [submodule] right now? It
would be better forward compatible with the proposed extension, it
would 'harmonize' the section names used in .gitmodules and
.git/config, and it would offer a clean break from what's currently
supported in 'master'.

--
larsh

^ permalink raw reply

* Re: [PATCH] Port git-tag.sh to C.
From: Robin Rosenberg @ 2007-06-10 12:37 UTC (permalink / raw)
  To: Kristian Høgsberg; +Cc: Junio C Hamano, git
In-Reply-To: <466B376D.8040303@redhat.com>

söndag 10 juni 2007 skrev Kristian Høgsberg:
> Junio C Hamano wrote:
> > Kristian Høgsberg <krh@redhat.com> writes:
> > 
> >> Content-Type: TEXT/PLAIN; charset=ISO-8859-1
> >>
> >> From: Kristian Høgsberg <krh@redhat.com>
> >>
> >> A more or less straight-forward port of git-tag.sh to C.
> >>
> >> Signed-off-by: Kristian Høgsberg <krh@redhat.com>
> >> Cc: Johannes Schindelin <Johannes.Schindelin@gmx.de>
> > 
> > I think your name in your commit message is in UTF-8 but munged your
> > mail was mismarked as iso-8859-1.
> 
> That's odd both the email I cc'ed to my redhat.com address and the one I got 
> on gmail.com through the list have
> 
>    Content-Type: text/plain; charset=utf-8
> 
> and saving the raw message and asking /usr/bin/file, it tells me its
> 
>    /home/krh/Desktop/hep: UTF-8 Unicode mail text

Maybe Junio's mail server has a problem with the impliction of this header line? 

"Content-Transfer-Encoding: 8bit" ?

-- robin

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox