changeset 525:721a8e30822b

Merge
author Chris Cannam
date Thu, 17 Nov 2011 17:12:39 +0000
parents eea753f1cae8 (current diff) d869e6a18f63 (diff)
children b80d28f2f456
files
diffstat 69 files changed, 1848 insertions(+), 142 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Nov 17 16:40:48 2011 +0000
+++ b/.hgignore	Thu Nov 17 17:12:39 2011 +0000
@@ -1,29 +1,28 @@
-syntax: glob
-*.core
-*.o
-*~
-*.exe
-*.dll
-*.pyc
-*.orig
-*.user
-moc_*
-qrc_*
-o/*
-core
-easyhg
-debug/*
-release/*
-Makefile
-Makefile.Debug
-Makefile.Release
-*.app/*
-.DS_Store
-*.pdb
-
-re:^EasyMercurial$
-re:^kdiff3$
-re:^_UpgradeReport_Files/
+syntax: glob
+*.core
+*.o
+*~
+*.exe
+*.dll
+*.pyc
+*.orig
+*.user
+moc_*
+qrc_*
+o/*
+core
+easyhg
+debug/*
+release/*
+Makefile
+Makefile.Debug
+Makefile.Release
+*.app/*
+.DS_Store
+*.pdb
+re:^EasyMercurial$
+re:^kdiff3$
+re:^_UpgradeReport_Files/
 *.dmg
 *.xcodeproj
 *.bak
--- a/.hgtags	Thu Nov 17 16:40:48 2011 +0000
+++ b/.hgtags	Thu Nov 17 17:12:39 2011 +0000
@@ -13,3 +13,4 @@
 a206deb6c1aab16f5bfb4c1d9d10074d1a93fa7e easyhg_v0.9.6
 9510a32a96ab9ea3c2bec5998f3f702b29ec0114 easyhg_v0.9.7
 319f920a51ee61df29701db8ad9bdb413c66a399 easyhg_v0.9.8
+6bb2a1f3087cd57d5a3296bbb82a494b7fa609c6 easyhg_v1.0
--- a/easyhg.pro	Thu Nov 17 16:40:48 2011 +0000
+++ b/easyhg.pro	Thu Nov 17 17:12:39 2011 +0000
@@ -1,5 +1,5 @@
 
-CONFIG += release
+CONFIG += debug
 
 TEMPLATE = app
 TARGET = EasyMercurial
@@ -63,7 +63,8 @@
     src/moreinformationdialog.h \
     src/annotatedialog.h \
     src/hgignoredialog.h \
-    src/versiontester.h
+    src/versiontester.h \
+    src/squeezedlabel.h
 SOURCES = \
     src/main.cpp \
     src/mainwindow.cpp \
@@ -99,7 +100,8 @@
     src/moreinformationdialog.cpp \
     src/annotatedialog.cpp \
     src/hgignoredialog.cpp \
-    src/versiontester.cpp
+    src/versiontester.cpp \
+    src/squeezedlabel.cpp
 
 macx-* {
     SOURCES += src/common_osx.mm
--- a/easyhg.py	Thu Nov 17 16:40:48 2011 +0000
+++ b/easyhg.py	Thu Nov 17 17:12:39 2011 +0000
@@ -13,7 +13,7 @@
 #    License, or (at your option) any later version.  See the file
 #    COPYING included with this distribution for more information.
 
-import sys, os, stat, urllib, urllib2, urlparse, platform, hashlib
+import sys, os, stat, urllib, urllib2, urlparse, hashlib
 
 from mercurial.i18n import _
 from mercurial import ui, util, error
@@ -135,7 +135,7 @@
         except:
             self.ui.write("failed to open authfile %s for writing\n" % self.auth_file)
             raise
-        if platform.system() != 'Windows':
+        if os.name == 'posix':
             try:
                 os.fchmod(ofp.fileno(), stat.S_IRUSR | stat.S_IWUSR)
             except:
--- a/easyhg.qrc	Thu Nov 17 16:40:48 2011 +0000
+++ b/easyhg.qrc	Thu Nov 17 17:12:39 2011 +0000
@@ -24,6 +24,27 @@
         <file>images/fileopen.png</file>
         <file>images/star.png</file>
         <file>images/easyhg-icon.png</file>
+        <file>images/home.png</file>
+        <file>images/back.png</file>
+        <file>images/forward.png</file>
+	<file>help/topics.html</file>
+	<file>help/help.css</file>
+	<file>help/a-04.html</file>
+	<file>help/a-10.html</file>
+	<file>help/a-11.html</file>
+	<file>help/a-12.html</file>
+	<file>help/a-13.html</file>
+	<file>help/a-20.html</file>
+	<file>help/a-21.html</file>
+	<file>help/a-22.html</file>
+	<file>help/a-23.html</file>
+	<file>help/a-30.html</file>
+	<file>help/a-31.html</file>
+	<file>help/a-32.html</file>
+	<file>help/a-33.html</file>
+	<file>help/images/openremote50.png</file>
+	<file>help/images/openfolder50.png</file>
+	<file>help/images/openlocal50.png</file>
 	<file>easyhg.py</file>
 	<file>easyhg_en.qm</file>
     </qresource>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-04.html	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,38 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>What is a repository?</h2>
+
+<p>When you use a version control system to keep track of your changes to
+a set of files, there are two different concepts you're dealing with:
+a <i>working copy</i> and a <i>repository</i>.</p>
+
+<p>A <i>working copy</i> is just a folder with your project's files in it.  It
+contains the versions of the files that you are working with now.
+<ul><li>EasyMercurial's &ldquo;My Work&rdquo; tab shows you which files you have been working on in your current working copy.</li></ul></p>
+
+<p>A <i>repository</i> is a record of the entire history of your project. When
+change something in the working copy, you can then commit it to the
+repository and your change gets added to the history.
+<ul><li>EasyMercurial's &ldquo;History&rdquo; tab shows you the changes that have been committed to your project's history in its repository.</li></ul></p>
+
+<p>You can also go back and grab an older version from the repository if
+you find you need it.  (If you do this, then the working copy will be
+updated so as to contain that older version rather than the most
+recent one.)</p>
+
+<p>Older centralised version control systems use a separate database for
+the repository.  But with a distributed version control system such as
+Mercurial, the repository &ndash; the entire history of your project files
+&ndash; is stowed into a special folder inside the working copy on your
+hard drive.  Every change you commit gets added to the history in that
+hidden folder. (The history is compressed, so it doesn't take as much
+space as you might imagine.)</p>
+
+<p>The term <i>remote repository</i> simply refers to a repository related to
+your local one, but stored on another computer somewhere else.  Often
+this may be a &ldquo;master copy&rdquo; of your project stored on a server
+elsewhere, which you and your collaborators can use to keep up with
+each other's work, or which you can use to make your work public, or
+simply use as a private backup.</p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-10.html	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,37 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>Someone gave me a repository URL and asked me to clone it</h2>
+
+<p>A Mercurial repository location is usually described by a URL, like
+that of a website.</p>
+
+<p>For example, the URL for the repository containing the source code for
+EasyMercurial itself is <code>https://bitbucket.org/cannam/easyhg</code>.</p>
+
+<p>To get a copy of the files in a repository, you need to <i>clone</i> the
+repository from the remote URL into a folder on your own computer.  To
+do this,</p>
+
+<p><b>1. Click the Open toolbar button or use File -> Open</b></p>
+
+<p><center><img src="images/openremote50.png"></center></p>
+
+<p><b>2. Select &ldquo;Remote repository&rdquo; as the thing you want to open</b></p>
+
+<p><b>3. Enter the repository URL into the URL field</b></p>
+
+<p><b>4. Give the name of a folder on your local computer to clone into</b> &ndash;
+ this folder will be created for you, so it shouldn't be one that already
+ exists</p>
+
+<p><b>5. Click OK</b></p>
+
+<p>If the remote repository has restricted access, you may be asked to
+provide a username and password to log in to the server it is hosted
+on.  If the repository is large, you may have to wait a while for all the
+data to be transferred.</p>
+
+<p>Provided the clone has been successful, you should now have a local
+repository to start working in.
+<ul><li>Note: the equivalent Mercurial command for this is <b>hg clone</b></li></ul>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-11.html	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,27 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I have a folder of source code or documents on my computer and I want to use version control to manage them</h2>
+
+<p>To start using version control for a project folder, you need to
+initialise a repository there.  EasyMercurial does this for you when
+you open the folder.</p>
+
+<p><b>1. Click the Open toolbar button or use File -> Open</b></p>
+
+<p><center><img src="images/openfolder50.png"></center></p>
+
+<p><b>2. Select &ldquo;File folder&rdquo; as the thing you want to open</b></p>
+
+<p><b>3. Browse to your folder</b></p>
+
+<p><b>4. Click OK</b></p>
+
+<p>A new repository will be created, stowed into the working folder you
+selected.  At first, it will have an empty history.  You can then
+start to add and commit changes to your files.</p>
+
+<p>(You will need to &ldquo;add&rdquo; files before you can start to track changes to
+them.  The default is for all files in the folder to be treated as
+&ldquo;untracked&rdquo;, i.e. not included in the history.)
+<ul><li>Note: the equivalent Mercurial command for this is <b>hg init</b></li></ul>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-12.html	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,22 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I want to start a new project using version control</h2>
+
+<p>You can initialise a repository in an empty folder, in order to start
+using version control for a new project:</p>
+
+<p><b>1. Click the Open toolbar button or use File -> Open</b></p>
+
+<p><center><img src="images/openfolder50.png"></center></p>
+
+<p><b>2. Select &ldquo;File folder&rdquo; as the thing you want to open</b></p>
+
+<p><b>3. Make a new folder in the file dialog and browse to it</b></p>
+
+<p><b>4. Click OK</b></p>
+
+<p>A new repository will be created, stowed into the empty working folder
+you selected.  At first, it will have an empty history.  You can then
+start to add files and commit changes to your files.
+<ul><li>Note: the equivalent Mercurial command for this is <b>hg init</b></li></ul>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-13.html	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,17 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I have a Mercurial repository on my local computer already and I want to use it with EasyMercurial</h2>
+
+<p>Just open it:</p>
+
+<p><b>1. Click the Open toolbar button or use File -> Open</b></p>
+
+<p><center><img src="images/openlocal50.png"></center></p>
+
+<p><b>2. Select &ldquo;Local repository&rdquo; as the thing you want to open</b></p>
+
+<p><b>3. Browse to the working folder for your local repository</b></p>
+
+<p><b>4. Click OK</b></p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-20.html	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,30 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I've added a new file: what do I do with it?</h2>
+
+<p>When you add a new file in the working folder, you normally want to
+ensure that Mercurial keeps track of changes to that file &ndash; and that
+the file is included in all copies of the repository.  To do this, you
+need to tell Mercurial to <i>track</i> the file by adding it to version
+control.</p>
+
+<p>EasyMercurial shows files that have been created but not added in the
+<b>&ldquo;Untracked&rdquo;</b> file list under &ldquo;My work&rdquo;.  (If your file is not listed
+there, try clicking the Refresh button.)</p>
+
+<p><b>1. Find the file you want to add in the Untracked list and select it</b></p>
+
+<p><b>2. Click Add in the toolbar on the left of the window</b></p>
+
+<p>The file will be moved to the <b>&ldquo;Added&rdquo;</b> list.  This tells Mercurial to
+track the file.  The next time you commit, the contents of your new
+file will be recorded as part of that change set.
+<ul><li>Note: the equivalent Mercurial command for this is <b>hg add</b></li></ul></p>
+
+<p>Of course, you don't always want to track every file in your working
+copy.  Object files generated by a compiler, output files from tests,
+etc should often not be included in version control.  You can ensure
+that such files don't show up in the Untracked list by right-clicking
+on them and choosing <b>&ldquo;Ignore..."</b>.</p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-21.html	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,22 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I have changed some files and I want to record the changes</h2>
+
+<p>Click the Commit button in the toolbar on the left to commit all of
+the changes you have made to tracked files in your working folder.
+That is, all files listed as <b>&ldquo;Modified&rdquo;</b>, <b>&ldquo;Removed&rdquo;</b>, or <b>&ldquo;Added&rdquo;</b>
+under &ldquo;My Work&rdquo;.</p>
+
+<p>(If the files you have changed are still listed as <b>&ldquo;Untracked&rdquo;</b>, then
+you must add them before you can commit.  See <a href="a-20.html">I've added a new file...</a>.</p>
+
+<p>When you commit your changes, you will be asked for a commit message
+which will accompany that change set in the history.  Enter something
+that will help you remember &ndash; and other readers understand &ndash; what
+you have changed and why.</p>
+
+<p>If you want to commit only some files, right-click on them in the list
+and choose Commit from the context menu.
+<ul><li>Note: the equivalent Mercurial command for this is <b>hg commit</b></li></ul></p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-22.html	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,33 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I want to remove, rename, or copy a file</h2>
+
+<p>To remove or rename a file, you first need to find it in the list of
+files under &ldquo;My work&rdquo;.  This area normally shows only those files that
+you have changed since your last commit: if it isn't one of those,
+toggle the &ldquo;Show all files&rdquo; option at the bottom of the window.</p>
+
+<p>To <b>remove</b> a file from version control so that changes to it are no longer tracked:</p>
+
+<p><b>1. Select the file you want to remove in the list of files under &ldquo;My work&rdquo;.</b></p>
+
+<p><b>2. Click Remove in the toolbar on the left of the window</b></p>
+
+<p>The file will be moved to the <b>&ldquo;Removed&rdquo;</b> list.  This tells Mercurial
+to stop tracking the file the next time you commit.  The file itself
+is not removed from the disc: you will need to do that using your
+system file manager afterwards.
+<ul><li>Note: the equivalent Mercurial command for this is <b>hg remove -Af</b></li></ul></p>
+
+<p>To <b>rename</b> or <b>copy</b> a file:</p>
+
+<p><b>1. Select the file you want to remove in the list of files under &ldquo;My work&rdquo;.</b></p>
+
+<p><b>2. Right-click and select &ldquo;Rename..." or &ldquo;Copy..." on the context menu</b></p>
+
+<p><b>3. Enter a new name for the file.</b>
+<ul><li>Note: the equivalent Mercurial commands are <b>hg rename</b> and <b>hg copy</b></li></ul></p>
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-23.html	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,19 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I just deleted a file using the system file manager &ndash; then remembered I hadn't told the version control tool about it &ndash; what now?</h2>
+
+<p>Any files that the version control system thinks should be there, but
+that can't be found in your working copy are shown as <b>&ldquo;Missing&rdquo;</b>
+under &ldquo;My work&rdquo;.  All you need to do is:</p>
+
+<p><b>1. Find your file in the &ldquo;Missing&rdquo; list and select it</b></p>
+
+<p><b>2. Click Remove in the toolbar on the left of the window</b></p>
+
+<p>This tells Mercurial that you haven't merely lost the file, but that
+you intended to remove it.  The next time you commit, it will be
+removed from tracking in version control.</p>
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-30.html	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,11 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I have committed some changes &ndash; how do I share them with my colleagues?</h2>
+
+<p>There are two common general approaches:</p>
+
+<p><a href="a-31.html">Permit your colleagues to &ldquo;pull&rdquo; your changes</a> directly from the local repository in your working folder, <i>or</i></p>
+
+<p><a href="a-32.html">&ldquo;Push&rdquo; your changes to a shared remote repository</a> which you can all use as a master copy.</p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-31.html	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,21 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I want to let my colleagues pull changes directly from my local repository</h2>
+
+<p>You can do this on a temporary basis using EasyMercurial, for
+occasional ad-hoc transfers:</p>
+
+<p><b>1. In EasyMercurial on your computer, go to File -> Share Repository and make a note of the URL shown in the window that is opened</b></p>
+
+<p><b>2. In EasyMercurial on your colleague's computer, either <a href="a-12.html">open a new empty folder</a>, or <a href="a-13.html">reopen a repository</a> that has been pulled from your repository in the past</b></p>
+
+<p><b>3. Go to Remote -> Set Remote Location on your colleague's computer and enter the URL you made a note of in the first step</b></p>
+
+<p><b>4. Click Pull in the main toolbar on your colleague's EasyMercurial program.</b></p>
+
+<p>There are various ways to set this relationship on a less temporary
+footing if you have shared access to your local folder, through
+network filesystems or via remote login to the computer you are using.
+Configuring this is outside the scope of this help document.</p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-32.html	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,37 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I want to put my changes into a master repository shared with my colleagues</h2>
+
+<p>Setting up such a repository with a properly configured remote server
+is out of the scope of this Help, but you generally want one of the
+following:</p>
+
+<p><b>A server that everyone on your team has secure ssh access to</b>, <i>or</i></p>
+
+<p><b>An account with a managed online Mercurial hosting service</b></p>
+
+<p>With either of the above, you should be able to create a new
+repository on the server and obtain a Mercurial URL for it.  That may
+be a <i>ssh://host/path</i> URL in the former case, or the URL (often an
+<i>https</i> one) provided by the service in the latter case.</p>
+
+<p>In EasyMercurial, you then:</p>
+
+<p><b>1. Go to Remote -> Set Remote Location.., enter the URL of the remote repository and click OK.</b>
+<ul><li>This tells EasyMercurial to use that URL as the default location for subsequent push and pull operations.</li></ul></p>
+
+<p><b>2. Click Push on the main toolbar at the top of the EasyMercurial window.</b></p>
+
+<p>This will push all of the changes that you have made in your local
+repository (since you pushed to the same target, if you ever have).
+You should do this regularly whenever you have a coherent set of
+changes for others to use or test.  Your colleagues can then pull from
+the same remote repository URL to obtain your changes.</p>
+
+<p>For this to work, the target repository must be <i>related</i> to the local
+one.  That means either a repository that has been pulled to, or
+pushed to from, the local repository before; or the repository that
+was initially used to clone the local one from; or else an empty
+repository.</p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-33.html	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,40 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I tried to push my changes, but it told me &ldquo;the remote repository may have been changed by someone else&rdquo; and refused</h2>
+
+<p>This indicates that the remote repository has some changes in it that
+you do not have in your local repository (and that are in branches
+that you have also changed).</p>
+
+<p>Perhaps someone else made these changes and pushed them, or they may
+have been pushed by you from a different computer.</p>
+
+<p><b>Why should that prevent me from pushing my changes?</b></p>
+
+<p>A good principle is that you should review and test your changes
+before you push them to another repository.  It may be OK to commit
+changes locally that don't really work or that aren't complete enough
+to test, but it's a bad idea to push anything that would cause the
+remote repository to have an untested set of changes in it.</p>
+
+<p>For this reason, if you change some files, someone else changes some
+others, and you both try to push them without knowing about the other
+one, Mercurial must refuse the second push &ndash; it can't simply merge
+the changes because the result might not make any sense.</p>
+
+<p>Instead you must pull the other person's changes and merge them
+locally before you push.  Fortunately, this is easy to do:</p>
+
+<p><b>1. Click Pull on the main toolbar at the top of the EasyMercurial window.</b>
+<ul><li>You should see that some changes are pulled and added to your local repository.  This will usually lead to a forked graph in the History pane, as your changes and the other user's were both started from the same parent at the same time.</li></ul></p>
+
+<p><b>2. Click Merge in the toolbar on the left.</b>
+<ul><li>Any changes that affect different files, or that affect different parts of the same file, will be merged automatically.  For changes that affect the same parts of the same file, you will be asked to choose which change to include in the merged copy.</li></ul></p>
+
+<p><b>3. Review or test the resulting merged version in your local working folder.</b></p>
+
+<p><b>4. Commit the merged version.</b></p>
+
+<p><b>5. Push again to the remote repository.</b></p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/generate.sh	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,53 @@
+#!/bin/bash
+
+css='<link rel="stylesheet" type="text/css" href="help.css"/>'
+
+echo "$css" > topics.html
+cat intro.html >> topics.html
+
+pcat=""
+
+for x in topics/*.txt ; do
+
+    b=`basename "$x" .txt`
+    out="a-$b.html"
+
+    echo "$css" > "$out"
+
+    cat "$x" | perl -e '
+$_ = join "", <>;
+s/^{[\w\s]+}//s;
+s/^(\s*)([A-Za-z][^\n]*)/$1<h2>$2<\/h2>/s;
+s/^\s+\*\s+(.*)$/<ul><li>$1<\/li><\/ul>/gm;
+s/\*([\w"][^\*]+)\*/<b>$1<\/b>/gs;
+s/"([\w])/&ldquo;$1/gs;
+s/([\w])"/$1&rdquo;/gs;
+s/^\#([^\s]+)$/<center><img src="images\/$1.png"><\/center>/gm;
+s/\n-+\n/\n/gs;
+s/\n\n([^\n])/\n\n<p>$1/gs;
+s/^\n*([^<\n])/\n<p>$1/gs;
+s/^\n*(<[^p])/\n<p>$1/gs;
+s/([^\n])\n\n/$1<\/p>\n\n/gs;
+s/([^>\n])\n*$/$1<\/p>\n\n/gs;
+s/\[\[([^\|]*)\|([^\]]*)\]\]/<a href="a-$1.html">$2<\/a>/gs;
+s/\[\[([^\|\]]*)\]\]/<a href="$1">$1<\/a>/gs;
+s/\b_([^_]+)_\b/<i>$1<\/i>/gs;
+s/@(\w[^@]+)@/<code>$1<\/code>/gs;
+s/---/&mdash;/gs;
+s/--/&ndash;/gs;
+s/<p><h2>/<h2>/gs;
+s/<\/h2><\/p>/<\/h2>/gs;
+print;
+' >> "$out"
+
+    category=`grep '^{.*}$' "$x" | sed 's/[{}]//g'`
+
+    if [ "$category" != "$pcat" ]; then
+	echo "<h3>$category</h3>" >> topics.html
+	pcat="$category"
+    fi
+
+    grep '<h2>' "$out" | sed "s|<h2>|<p><a href=\"$out\">|" | sed 's/<\/h2>/<\/a><\/p>/' >> topics.html
+
+done
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/help.css	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,59 @@
+
+body {
+  background: #fdfaf0;
+  color: #3e442c;
+  margin: 0;
+  padding: 0;
+  margin-bottom: 40px;
+  font-family: sans-serif;
+}
+
+h1, h2, h3, h4 {
+  margin-left: 10px;
+  margin-bottom: 0.4em;
+}
+
+h2 {
+  font-size: 1.3em;
+  margin-top: 0;
+}
+
+h3 {
+  font-size: 1.2em;
+}
+
+h4 { 
+  font-size: 1.1em;
+}
+
+ol, ul, ol li, ul li {
+  margin-left: 0;
+  color: #808080;
+  font-style: italic;
+}
+
+p, pre { 
+  margin-left: 20px;
+  margin-bottom: 0.5em;
+  margin-right: 10px;
+  margin-top: 0;
+}
+
+blockquote {
+  margin-left: 70px;
+}
+
+table { 
+  padding-top: 0;
+}
+
+a {
+  color: #be5700;
+  text-decoration: none;
+}
+
+a:hover {
+  text-decoration: underline;
+}
+
+
Binary file help/images/openfolder.png has changed
Binary file help/images/openfolder50.png has changed
Binary file help/images/openlocal.png has changed
Binary file help/images/openlocal50.png has changed
Binary file help/images/openremote.png has changed
Binary file help/images/openremote50.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/intro.html	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,4 @@
+
+<h1>Quick Topics</h1>
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics.html	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,22 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+<h1>Quick Topics</h1>
+
+
+<h3>Terminology</h3>
+<p><a href="a-04.html">What is a repository?</a></p>
+<h3>Opening and initialising things</h3>
+<p><a href="a-10.html">Someone gave me a repository URL and asked me to clone it</a></p>
+<p><a href="a-11.html">I have a folder of source code or documents on my computer and I want to use version control to manage them</a></p>
+<p><a href="a-12.html">I want to start a new project using version control</a></p>
+<p><a href="a-13.html">I have a Mercurial repository on my local computer already and I want to use it with EasyMercurial</a></p>
+<h3>Making changes</h3>
+<p><a href="a-20.html">I've added a new file: what do I do with it?</a></p>
+<p><a href="a-21.html">I have changed some files and I want to record the changes</a></p>
+<p><a href="a-22.html">I want to remove, rename, or copy a file</a></p>
+<p><a href="a-23.html">I just deleted a file using the system file manager &ndash; then remembered I hadn't told the version control tool about it &ndash; what now?</a></p>
+<h3>Sharing changes</h3>
+<p><a href="a-30.html">I have committed some changes &ndash; how do I share them with my colleagues?</a></p>
+<p><a href="a-31.html">I want to let my colleagues pull changes directly from my local repository</a></p>
+<p><a href="a-32.html">I want to put my changes into a master repository shared with my colleagues</a></p>
+<p><a href="a-33.html">I tried to push my changes, but it told me &ldquo;the remote repository may have been changed by someone else&rdquo; and refused</a></p>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/04.txt	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,39 @@
+{Terminology}
+
+What is a repository?
+
+When you use a version control system to keep track of your changes to
+a set of files, there are two different concepts you're dealing with:
+a _working copy_ and a _repository_.
+
+A _working copy_ is just a folder with your project's files in it.  It
+contains the versions of the files that you are working with now.
+
+ * EasyMercurial's "My Work" tab shows you which files you have been working on in your current working copy.
+
+A _repository_ is a record of the entire history of your project. When
+change something in the working copy, you can then commit it to the
+repository and your change gets added to the history.
+
+ * EasyMercurial's "History" tab shows you the changes that have been committed to your project's history in its repository.
+
+You can also go back and grab an older version from the repository if
+you find you need it.  (If you do this, then the working copy will be
+updated so as to contain that older version rather than the most
+recent one.)
+
+Older centralised version control systems use a separate database for
+the repository.  But with a distributed version control system such as
+Mercurial, the repository -- the entire history of your project files
+-- is stowed into a special folder inside the working copy on your
+hard drive.  Every change you commit gets added to the history in that
+hidden folder. (The history is compressed, so it doesn't take as much
+space as you might imagine.)
+
+The term _remote repository_ simply refers to a repository related to
+your local one, but stored on another computer somewhere else.  Often
+this may be a "master copy" of your project stored on a server
+elsewhere, which you and your collaborators can use to keep up with
+each other's work, or which you can use to make your work public, or
+simply use as a private backup.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/10.txt	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,37 @@
+{Opening and initialising things}
+
+Someone gave me a repository URL and asked me to clone it
+
+A Mercurial repository location is usually described by a URL, like
+that of a website.
+
+For example, the URL for the repository containing the source code for
+EasyMercurial itself is @https://bitbucket.org/cannam/easyhg@.
+
+To get a copy of the files in a repository, you need to _clone_ the
+repository from the remote URL into a folder on your own computer.  To
+do this,
+
+*1. Click the Open toolbar button or use File -> Open*
+
+#openremote50
+
+*2. Select "Remote repository" as the thing you want to open*
+
+*3. Enter the repository URL into the URL field*
+
+*4. Give the name of a folder on your local computer to clone into* --
+ this folder will be created for you, so it shouldn't be one that already
+ exists
+
+*5. Click OK*
+
+If the remote repository has restricted access, you may be asked to
+provide a username and password to log in to the server it is hosted
+on.  If the repository is large, you may have to wait a while for all the
+data to be transferred.
+
+Provided the clone has been successful, you should now have a local
+repository to start working in.
+
+ * Note: the equivalent Mercurial command for this is *hg clone*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/11.txt	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,27 @@
+{Opening and initialising things}
+
+I have a folder of source code or documents on my computer and I want to use version control to manage them
+
+To start using version control for a project folder, you need to
+initialise a repository there.  EasyMercurial does this for you when
+you open the folder.
+
+*1. Click the Open toolbar button or use File -> Open*
+
+#openfolder50
+
+*2. Select "File folder" as the thing you want to open*
+
+*3. Browse to your folder*
+
+*4. Click OK*
+
+A new repository will be created, stowed into the working folder you
+selected.  At first, it will have an empty history.  You can then
+start to add and commit changes to your files.
+
+(You will need to "add" files before you can start to track changes to
+them.  The default is for all files in the folder to be treated as
+"untracked", i.e. not included in the history.)
+
+ * Note: the equivalent Mercurial command for this is *hg init*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/12.txt	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,22 @@
+{Opening and initialising things}
+
+I want to start a new project using version control
+
+You can initialise a repository in an empty folder, in order to start
+using version control for a new project:
+
+*1. Click the Open toolbar button or use File -> Open*
+
+#openfolder50
+
+*2. Select "File folder" as the thing you want to open*
+
+*3. Make a new folder in the file dialog and browse to it*
+
+*4. Click OK*
+
+A new repository will be created, stowed into the empty working folder
+you selected.  At first, it will have an empty history.  You can then
+start to add files and commit changes to your files.
+
+ * Note: the equivalent Mercurial command for this is *hg init*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/13.txt	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,16 @@
+{Opening and initialising things}
+
+I have a Mercurial repository on my local computer already and I want to use it with EasyMercurial
+
+Just open it:
+
+*1. Click the Open toolbar button or use File -> Open*
+
+#openlocal50
+
+*2. Select "Local repository" as the thing you want to open*
+
+*3. Browse to the working folder for your local repository*
+
+*4. Click OK*
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/20.txt	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,30 @@
+{Making changes}
+
+I've added a new file: what do I do with it?
+
+When you add a new file in the working folder, you normally want to
+ensure that Mercurial keeps track of changes to that file -- and that
+the file is included in all copies of the repository.  To do this, you
+need to tell Mercurial to _track_ the file by adding it to version
+control.
+
+EasyMercurial shows files that have been created but not added in the
+*"Untracked"* file list under "My work".  (If your file is not listed
+there, try clicking the Refresh button.)
+
+*1. Find the file you want to add in the Untracked list and select it*
+
+*2. Click Add in the toolbar on the left of the window*
+
+The file will be moved to the *"Added"* list.  This tells Mercurial to
+track the file.  The next time you commit, the contents of your new
+file will be recorded as part of that change set.
+
+ * Note: the equivalent Mercurial command for this is *hg add*
+
+Of course, you don't always want to track every file in your working
+copy.  Object files generated by a compiler, output files from tests,
+etc should often not be included in version control.  You can ensure
+that such files don't show up in the Untracked list by right-clicking
+on them and choosing *"Ignore..."*.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/21.txt	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,22 @@
+{Making changes}
+
+I have changed some files and I want to record the changes
+
+Click the Commit button in the toolbar on the left to commit all of
+the changes you have made to tracked files in your working folder.
+That is, all files listed as *"Modified"*, *"Removed"*, or *"Added"*
+under "My Work".
+
+(If the files you have changed are still listed as *"Untracked"*, then
+you must add them before you can commit.  See [[20|I've added a new file...]].
+
+When you commit your changes, you will be asked for a commit message
+which will accompany that change set in the history.  Enter something
+that will help you remember -- and other readers understand -- what
+you have changed and why.
+
+If you want to commit only some files, right-click on them in the list
+and choose Commit from the context menu.
+
+ * Note: the equivalent Mercurial command for this is *hg commit*
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/22.txt	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,34 @@
+{Making changes}
+
+I want to remove, rename, or copy a file
+
+To remove or rename a file, you first need to find it in the list of
+files under "My work".  This area normally shows only those files that
+you have changed since your last commit: if it isn't one of those,
+toggle the "Show all files" option at the bottom of the window.
+
+To *remove* a file from version control so that changes to it are no longer tracked:
+
+*1. Select the file you want to remove in the list of files under "My work".*
+
+*2. Click Remove in the toolbar on the left of the window*
+
+The file will be moved to the *"Removed"* list.  This tells Mercurial
+to stop tracking the file the next time you commit.  The file itself
+is not removed from the disc: you will need to do that using your
+system file manager afterwards.
+
+ * Note: the equivalent Mercurial command for this is *hg remove -Af*
+
+To *rename* or *copy* a file:
+
+*1. Select the file you want to remove in the list of files under "My work".*
+
+*2. Right-click and select "Rename..." or "Copy..." on the context menu*
+
+*3. Enter a new name for the file.*
+
+ * Note: the equivalent Mercurial commands are *hg rename* and *hg copy*
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/23.txt	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,18 @@
+{Making changes}
+
+I just deleted a file using the system file manager -- then remembered I hadn't told the version control tool about it -- what now?
+
+Any files that the version control system thinks should be there, but
+that can't be found in your working copy are shown as *"Missing"*
+under "My work".  All you need to do is:
+
+*1. Find your file in the "Missing" list and select it*
+
+*2. Click Remove in the toolbar on the left of the window*
+
+This tells Mercurial that you haven't merely lost the file, but that
+you intended to remove it.  The next time you commit, it will be
+removed from tracking in version control.
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/30.txt	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,10 @@
+{Sharing changes}
+
+I have committed some changes -- how do I share them with my colleagues?
+
+There are two common general approaches:
+
+[[31|Permit your colleagues to "pull" your changes]] directly from the local repository in your working folder, _or_
+
+[[32|"Push" your changes to a shared remote repository]] which you can all use as a master copy.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/31.txt	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,20 @@
+{Sharing changes}
+
+I want to let my colleagues pull changes directly from my local repository
+
+You can do this on a temporary basis using EasyMercurial, for
+occasional ad-hoc transfers:
+
+*1. In EasyMercurial on your computer, go to File -> Share Repository and make a note of the URL shown in the window that is opened*
+
+*2. In EasyMercurial on your colleague's computer, either [[12|open a new empty folder]], or [[13|reopen a repository]] that has been pulled from your repository in the past*
+
+*3. Go to Remote -> Set Remote Location on your colleague's computer and enter the URL you made a note of in the first step*
+
+*4. Click Pull in the main toolbar on your colleague's EasyMercurial program.*
+
+There are various ways to set this relationship on a less temporary
+footing if you have shared access to your local folder, through
+network filesystems or via remote login to the computer you are using.
+Configuring this is outside the scope of this help document.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/32.txt	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,37 @@
+{Sharing changes}
+
+I want to put my changes into a master repository shared with my colleagues
+
+Setting up such a repository with a properly configured remote server
+is out of the scope of this Help, but you generally want one of the
+following:
+
+*A server that everyone on your team has secure ssh access to*, _or_
+
+*An account with a managed online Mercurial hosting service*
+
+With either of the above, you should be able to create a new
+repository on the server and obtain a Mercurial URL for it.  That may
+be a _ssh://host/path_ URL in the former case, or the URL (often an
+_https_ one) provided by the service in the latter case.
+
+In EasyMercurial, you then:
+
+*1. Go to Remote -> Set Remote Location.., enter the URL of the remote repository and click OK.*
+
+ * This tells EasyMercurial to use that URL as the default location for subsequent push and pull operations.
+
+*2. Click Push on the main toolbar at the top of the EasyMercurial window.*
+
+This will push all of the changes that you have made in your local
+repository (since you pushed to the same target, if you ever have).
+You should do this regularly whenever you have a coherent set of
+changes for others to use or test.  Your colleagues can then pull from
+the same remote repository URL to obtain your changes.
+
+For this to work, the target repository must be _related_ to the local
+one.  That means either a repository that has been pulled to, or
+pushed to from, the local repository before; or the repository that
+was initially used to clone the local one from; or else an empty
+repository.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/33.txt	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,41 @@
+{Sharing changes}
+
+I tried to push my changes, but it told me "the remote repository may have been changed by someone else" and refused
+
+This indicates that the remote repository has some changes in it that
+you do not have in your local repository (and that are in branches
+that you have also changed).
+
+Perhaps someone else made these changes and pushed them, or they may
+have been pushed by you from a different computer.
+
+*Why should that prevent me from pushing my changes?*
+
+A good principle is that you should review and test your changes
+before you push them to another repository.  It may be OK to commit
+changes locally that don't really work or that aren't complete enough
+to test, but it's a bad idea to push anything that would cause the
+remote repository to have an untested set of changes in it.
+
+For this reason, if you change some files, someone else changes some
+others, and you both try to push them without knowing about the other
+one, Mercurial must refuse the second push -- it can't simply merge
+the changes because the result might not make any sense.
+
+Instead you must pull the other person's changes and merge them
+locally before you push.  Fortunately, this is easy to do:
+
+*1. Click Pull on the main toolbar at the top of the EasyMercurial window.*
+
+ * You should see that some changes are pulled and added to your local repository.  This will usually lead to a forked graph in the History pane, as your changes and the other user's were both started from the same parent at the same time.
+
+*2. Click Merge in the toolbar on the left.*
+
+ * Any changes that affect different files, or that affect different parts of the same file, will be merged automatically.  For changes that affect the same parts of the same file, you will be asked to choose which change to include in the merged copy.
+
+*3. Review or test the resulting merged version in your local working folder.*
+
+*4. Commit the merged version.*
+
+*5. Push again to the remote repository.*
+
Binary file images/back.png has changed
Binary file images/forward.png has changed
Binary file images/home.png has changed
--- a/src/changeset.cpp	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/changeset.cpp	Thu Nov 17 17:12:39 2011 +0000
@@ -21,7 +21,8 @@
 
 #include <QVariant>
 
-Changeset::Changeset(const LogEntry &e)
+Changeset::Changeset(const LogEntry &e) :
+    m_closed(false)
 {
     foreach (QString key, e.keys()) {
         if (key == "parents") {
@@ -32,6 +33,10 @@
             QStringList tags = e.value(key).split
                 (" ", QString::SkipEmptyParts);
             setTags(tags);
+        } else if (key == "bookmarks") {
+            QStringList bmarks = e.value(key).split
+                (" ", QString::SkipEmptyParts);
+            setBookmarks(bmarks);
         } else if (key == "timestamp") {
             setTimestamp(e.value(key).split(" ")[0].toULongLong());
         } else if (key == "changeset") {
@@ -44,7 +49,7 @@
 
 QString Changeset::getLogTemplate()
 {
-    return "id: {rev}:{node|short}\\nauthor: {author}\\nbranch: {branches}\\ntag: {tags}\\ndatetime: {date|isodate}\\ntimestamp: {date|hgdate}\\nage: {date|age}\\nparents: {parents}\\ncomment: {desc|json}\\n\\n";
+    return "id: {rev}:{node|short}\\nauthor: {author}\\nbranch: {branches}\\ntag: {tags}\\nbookmarks: {bookmarks}\\ndatetime: {date|isodate}\\ntimestamp: {date|hgdate}\\nage: {date|age}\\nparents: {parents}\\ncomment: {desc|json}\\n\\n";
 }
 
 QString Changeset::formatHtml()
@@ -70,13 +75,15 @@
 	      << "datetime"
 	      << "branch"
 	      << "tags"
+	      << "bookmarks"
 	      << "comment";
 
     propTexts << QObject::tr("Identifier:")
 	      << QObject::tr("Author:")
 	      << QObject::tr("Date:")
 	      << QObject::tr("Branch:")
-	      << QObject::tr("Tag:")
+	      << QObject::tr("Tags:")
+	      << QObject::tr("Bookmarks:")
 	      << QObject::tr("Comment:");
 
     for (int i = 0; i < propNames.size(); ++i) {
@@ -88,6 +95,8 @@
             value = c;
         } else if (prop == "tags") {
             value = tags().join(" ");
+        } else if (prop == "bookmarks") {
+            value = bookmarks().join(" ");
         } else {
 	    value = xmlEncode(property(prop.toLocal8Bit().data()).toString());
 	}
--- a/src/changeset.h	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/changeset.h	Thu Nov 17 17:12:39 2011 +0000
@@ -38,6 +38,7 @@
     Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged STORED true);
     Q_PROPERTY(QString branch READ branch WRITE setBranch NOTIFY branchChanged STORED true);
     Q_PROPERTY(QStringList tags READ tags WRITE setTags NOTIFY tagsChanged STORED true);
+    Q_PROPERTY(QStringList bookmarks READ bookmarks WRITE setBookmarks NOTIFY bookmarksChanged STORED true);
     Q_PROPERTY(QString datetime READ datetime WRITE setDatetime NOTIFY datetimeChanged STORED true);
     Q_PROPERTY(qulonglong timestamp READ timestamp WRITE setTimestamp NOTIFY timestampChanged STORED true);
     Q_PROPERTY(QString age READ age WRITE setAge NOTIFY ageChanged STORED true);
@@ -53,6 +54,7 @@
     QString author() const { return m_author; }
     QString branch() const { return m_branch; }
     QStringList tags() const { return m_tags; }
+    QStringList bookmarks() const { return m_bookmarks; }
     QString datetime() const { return m_datetime; }
     qulonglong timestamp() const { return m_timestamp; }
     QString age() const { return m_age; }
@@ -65,6 +67,12 @@
      */
     QStringList children() const { return m_children; }
 
+    /**
+     * The closed property is not obtained from Hg, but set in
+     * Grapher::layout() based on traversing closed branch graphs
+     */
+    bool closed() const { return m_closed; }
+
     int number() const {
         return id().split(':')[0].toInt();
     }
@@ -114,11 +122,13 @@
     void authorChanged(QString author);
     void branchChanged(QString branch);
     void tagsChanged(QStringList tags);
+    void bookmarksChanged(QStringList bookmarks);
     void datetimeChanged(QString datetime);
     void timestampChanged(qulonglong timestamp);
     void ageChanged(QString age);
     void parentsChanged(QStringList parents);
     void childrenChanged(QStringList children);
+    void closedChanged(bool closed);
     void commentChanged(QString comment);
 
 public slots:
@@ -127,12 +137,15 @@
     void setBranch(QString branch) { m_branch = branch; emit branchChanged(branch); }
     void setTags(QStringList tags) { m_tags = tags; emit tagsChanged(tags); }
     void addTag(QString tag) { m_tags.push_back(tag); emit tagsChanged(m_tags); }
+    void setBookmarks(QStringList bmarks) { m_bookmarks = bmarks; emit bookmarksChanged(bmarks); }
+    void addBookmark(QString b) { m_bookmarks.push_back(b); emit bookmarksChanged(m_bookmarks); }
     void setDatetime(QString datetime) { m_datetime = datetime; emit datetimeChanged(datetime); }
     void setTimestamp(qulonglong timestamp) { m_timestamp = timestamp; emit timestampChanged(timestamp); }
     void setAge(QString age) { m_age = age; emit ageChanged(age); }
     void setParents(QStringList parents) { m_parents = parents; emit parentsChanged(parents); }
     void setChildren(QStringList children) { m_children = children; emit childrenChanged(m_children); }
     void addChild(QString child) { m_children.push_back(child); emit childrenChanged(m_children); }
+    void setClosed(bool closed) { m_closed = closed; emit closedChanged(closed); }
     void setComment(QString comment) { m_comment = comment; emit commentChanged(comment); }
 
 private:
@@ -140,11 +153,13 @@
     QString m_author;
     QString m_branch;
     QStringList m_tags;
+    QStringList m_bookmarks;
     QString m_datetime;
     qulonglong m_timestamp;
     QString m_age;
     QStringList m_parents;
     QStringList m_children;
+    bool m_closed;
     QString m_comment;
 };
 
--- a/src/changesetitem.cpp	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/changesetitem.cpp	Thu Nov 17 17:12:39 2011 +0000
@@ -38,7 +38,7 @@
 ChangesetItem::ChangesetItem(Changeset *cs) :
     m_changeset(cs), m_detail(0),
     m_showBranch(false), m_column(0), m_row(0), m_wide(false),
-    m_current(false), m_new(false)
+    m_current(false), m_closing(false), m_new(false)
 {
     m_font = QFont();
     m_font.setPixelSize(11);
@@ -72,7 +72,7 @@
     scene()->addItem(m_detail);
     int w = 100;
     if (m_wide) w = 180;
-    if (isMerge()) w = 60;
+    if (isMerge() || isClosingCommit()) w = 60;
     int h = 80;
 //    m_detail->moveBy(x() - (m_detail->boundingRect().width() - 50) / 2,
 //                     y() + 60);
@@ -197,6 +197,10 @@
     branch->setEnabled(m_current);
     connect(branch, SIGNAL(triggered()), this, SLOT(newBranchActivated()));
 
+    QAction *closebranch = menu->addAction(tr("Close branch..."));
+    closebranch->setEnabled(m_current);
+    connect(closebranch, SIGNAL(triggered()), this, SLOT(closeBranchActivated()));
+
     QAction *tag = menu->addAction(tr("Add tag..."));
     connect(tag, SIGNAL(triggered()), this, SLOT(tagActivated()));
 
@@ -240,12 +244,13 @@
 void ChangesetItem::mergeActivated() { emit mergeFrom(getId()); }
 void ChangesetItem::tagActivated() { emit tag(getId()); }
 void ChangesetItem::newBranchActivated() { emit newBranch(getId()); }
+void ChangesetItem::closeBranchActivated() { emit closeBranch(getId()); }
 
 void
 ChangesetItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *, QWidget *)
 {
-    if (isMerge()) {
-        paintMerge(paint);
+    if (isClosingCommit() || isMerge()) {
+        paintSimple(paint);
     } else {
         paintNormal(paint);
     }
@@ -257,15 +262,27 @@
     return (m_changeset && m_changeset->parents().size() > 1);
 }
 
+bool
+ChangesetItem::isClosed() const
+{
+    return (m_changeset && m_changeset->closed());
+}
+
 void
 ChangesetItem::paintNormal(QPainter *paint)
 {
     paint->save();
     
+    int alpha = 255;
+    if (isClosed()) alpha = 90;
+
     ColourSet *colourSet = ColourSet::instance();
     QColor branchColour = colourSet->getColourFor(m_changeset->branch());
     QColor userColour = colourSet->getColourFor(m_changeset->author());
 
+    branchColour.setAlpha(alpha);
+    userColour.setAlpha(alpha);
+
     QFont f(m_font);
 
     QTransform t = paint->worldTransform();
@@ -321,6 +338,40 @@
     int height = (lineCount + 1) * fh + 2;
     QRectF r(x0, 0, width - 3, height);
 
+    QColor textColour = Qt::black;
+    textColour.setAlpha(alpha);
+
+    if (m_showBranch && showText) {
+	// write branch name
+        paint->save();
+	f.setBold(true);
+	paint->setFont(f);
+	paint->setPen(QPen(branchColour));
+	QString branch = m_changeset->branch();
+        if (branch == "") branch = "default";
+	int wid = width - 3;
+	branch = TextAbbrev::abbreviate(branch, QFontMetrics(f), wid);
+	paint->drawText(x0, -fh + fm.ascent() - 4, branch);
+	f.setBold(false);
+        paint->restore();
+    }
+
+    QStringList bookmarks = m_changeset->bookmarks();
+    if (!bookmarks.empty() && showText) {
+        QString bmText = bookmarks.join(" ").trimmed();
+        int bw = fm.width(bmText);
+        int bx = x0 + width - bw - 14;
+        if (m_current) bx = bx - fh*1.5 + 3;
+        paint->save();
+        paint->setPen(QPen(branchColour, 2));
+//        paint->setBrush(QBrush(Qt::white));
+        paint->setBrush(QBrush(branchColour));
+        paint->drawRoundedRect(QRectF(bx, -fh - 4, bw + 4, fh * 2), 5, 5);
+        paint->setPen(QPen(Qt::white));
+        paint->drawText(bx + 2, -fh + fm.ascent() - 4, bmText);
+        paint->restore();
+    }
+
     if (showProperLines) {
 
         if (m_new) {
@@ -363,7 +414,7 @@
                                             fm, textwid);
     paint->drawText(x0 + 3, fm.ascent(), person);
 
-    paint->setPen(QPen(Qt::black));
+    paint->setPen(QPen(textColour));
 
     QStringList tags = m_changeset->tags();
     if (!tags.empty()) {
@@ -391,21 +442,6 @@
     paint->setBrush(Qt::NoBrush);
     paint->drawRoundedRect(r, 7, 7);
 
-    if (m_showBranch) {
-	// write branch name
-        paint->save();
-	f.setBold(true);
-	paint->setFont(f);
-	paint->setPen(QPen(branchColour));
-	QString branch = m_changeset->branch();
-        if (branch == "") branch = "default";
-	int wid = width - 3;
-	branch = TextAbbrev::abbreviate(branch, QFontMetrics(f), wid);
-	paint->drawText(x0, -fh + fm.ascent() - 4, branch);
-	f.setBold(false);
-        paint->restore();
-    }
-
     if (m_current && showProperLines) {
         paint->setRenderHint(QPainter::SmoothPixmapTransform, true);
         int starSize = fh * 1.5;
@@ -424,14 +460,20 @@
 }
 
 void
-ChangesetItem::paintMerge(QPainter *paint)
+ChangesetItem::paintSimple(QPainter *paint)
 {
     paint->save();
+
+    int alpha = 255;
+    if (isClosed()) alpha = 90;
     
     ColourSet *colourSet = ColourSet::instance();
     QColor branchColour = colourSet->getColourFor(m_changeset->branch());
     QColor userColour = colourSet->getColourFor(m_changeset->author());
 
+    branchColour.setAlpha(alpha);
+    userColour.setAlpha(alpha);
+
     QFont f(m_font);
 
     QTransform t = paint->worldTransform();
@@ -466,19 +508,31 @@
     if (showProperLines) {
 
         if (m_current) {
-            paint->drawEllipse(QRectF(x0 - 4, fh - 4, size + 8, size + 8));
-
+            if (isClosingCommit()) {
+                paint->drawRect(QRectF(x0 - 4, fh - 4, size + 8, size + 8));
+            } else {
+                paint->drawEllipse(QRectF(x0 - 4, fh - 4, size + 8, size + 8));
+            }
+            
             if (m_new) {
                 paint->save();
                 paint->setPen(Qt::yellow);
                 paint->setBrush(Qt::NoBrush);
-                paint->drawEllipse(QRectF(x0 - 2, fh - 2, size + 4, size + 4));
+                if (isClosingCommit()) {
+                    paint->drawRect(QRectF(x0 - 2, fh - 2, size + 4, size + 4));
+                } else {
+                    paint->drawEllipse(QRectF(x0 - 2, fh - 2, size + 4, size + 4));
+                }
                 paint->restore();
             }
         }
     }
 
-    paint->drawEllipse(QRectF(x0, fh, size, size));
+    if (isClosingCommit()) {
+        paint->drawRect(QRectF(x0, fh, size, size));
+    } else {
+        paint->drawEllipse(QRectF(x0, fh, size, size));
+    }
 
     if (m_showBranch) {
 	// write branch name
--- a/src/changesetitem.h	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/changesetitem.h	Thu Nov 17 17:12:39 2011 +0000
@@ -51,6 +51,14 @@
     bool isCurrent() const { return m_current; }
     void setCurrent(bool c) { m_current = c; }
 
+    // Closed is true if this changeset is on a closed branch
+    bool isClosed() const;
+
+    // Closing is true if this changeset is the commit that closed its
+    // branch (i.e. is at the end of a closed branch)
+    bool isClosingCommit() const { return m_closing; }
+    void setClosingCommit(bool c) { m_closing = c; }
+
     bool isNew() const { return m_new; }
     void setNew(bool n) { m_new = n; }
 
@@ -67,6 +75,7 @@
     void showSummary(Changeset *);
     void mergeFrom(QString);
     void newBranch(QString);
+    void closeBranch(QString);
     void tag(QString);
 
 public slots:
@@ -82,6 +91,7 @@
     void mergeActivated();
     void tagActivated();
     void newBranchActivated();
+    void closeBranchActivated();
 
 protected:
     virtual void mousePressEvent(QGraphicsSceneMouseEvent *);
@@ -98,6 +108,7 @@
     int m_row;
     bool m_wide;
     bool m_current;
+    bool m_closing;
     bool m_new;
 
     QMap<QAction *, QString> m_parentDiffActions;
@@ -107,7 +118,7 @@
 
     bool isMerge() const;
     void paintNormal(QPainter *);
-    void paintMerge(QPainter *);
+    void paintSimple(QPainter *);
 };
 
 #endif // CHANGESETITEM_H
--- a/src/changesetscene.cpp	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/changesetscene.cpp	Thu Nov 17 17:12:39 2011 +0000
@@ -60,6 +60,9 @@
     connect(item, SIGNAL(newBranch(QString)),
             this, SIGNAL(newBranch(QString)));
 
+    connect(item, SIGNAL(closeBranch(QString)),
+            this, SIGNAL(closeBranch(QString)));
+
     connect(item, SIGNAL(tag(QString)),
             this, SIGNAL(tag(QString)));
 }
--- a/src/changesetscene.h	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/changesetscene.h	Thu Nov 17 17:12:39 2011 +0000
@@ -66,6 +66,7 @@
     void diffToCurrent(QString id);
     void mergeFrom(QString id);
     void newBranch(QString id);
+    void closeBranch(QString id);
     void tag(QString id);
 
 private slots:
--- a/src/clickablelabel.h	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/clickablelabel.h	Thu Nov 17 17:12:39 2011 +0000
@@ -18,9 +18,11 @@
 #ifndef _CLICKABLE_LABEL_H_
 #define _CLICKABLE_LABEL_H_
 
-#include <QLabel>
+#include "squeezedlabel.h"
 
-class ClickableLabel : public QLabel
+#include <QMouseEvent>
+
+class ClickableLabel : public SqueezedLabel
 {
     Q_OBJECT
 
@@ -28,12 +30,12 @@
 
 public:
     ClickableLabel(const QString &text, QWidget *parent = 0) :
-        QLabel(text, parent),
+        SqueezedLabel(text, parent),
 	m_naturalText(text)
     { }
 
     ClickableLabel(QWidget *parent = 0) :
-	QLabel(parent)
+	SqueezedLabel(parent)
     { }
 
     ~ClickableLabel()
@@ -41,7 +43,7 @@
 
     void setText(const QString &t) {
 	m_naturalText = t;
-	QLabel::setText(t);
+	SqueezedLabel::setText(t);
     }
 
     bool mouseUnderline() const {
@@ -62,18 +64,22 @@
 protected:
     virtual void enterEvent(QEvent *) {
 	if (m_mouseUnderline) {
-	    QLabel::setText(tr("<u>%1</u>").arg(m_naturalText));
+	    SqueezedLabel::setText(tr("<u>%1</u>").arg(m_naturalText));
 	}
     }
 
     virtual void leaveEvent(QEvent *) {
 	if (m_mouseUnderline) {
-	    QLabel::setText(m_naturalText);
+	    SqueezedLabel::setText(m_naturalText);
 	}
     }
 
-    virtual void mousePressEvent(QMouseEvent *) {
-        emit clicked();
+    virtual void mousePressEvent(QMouseEvent *ev) {
+        if (ev->button() == Qt::LeftButton) {
+            emit clicked();
+        } else {
+            SqueezedLabel::mousePressEvent(ev);
+        }
     }
 
 private:
--- a/src/confirmcommentdialog.cpp	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/confirmcommentdialog.cpp	Thu Nov 17 17:12:39 2011 +0000
@@ -91,10 +91,15 @@
     else text = "<qt>" + intro + "<p>";
 
     text += "<code>";
-    foreach (QString file, files) {
-        text += "&nbsp;&nbsp;&nbsp;" + xmlEncode(file) + "<br>";
+    if (files.empty()) {
+        text += "&nbsp;&nbsp;&nbsp;</code>";
+        text += tr("(no files: metadata only)");
+    } else {
+        foreach (QString file, files) {
+            text += "&nbsp;&nbsp;&nbsp;" + xmlEncode(file) + "<br>";
+        }
+        text += "</code></qt>";
     }
-    text += "</code></qt>";
 
     return text;
 }
--- a/src/connectionitem.cpp	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/connectionitem.cpp	Thu Nov 17 17:12:39 2011 +0000
@@ -21,18 +21,19 @@
 #include "changesetitem.h"
 #include "changeset.h"
 #include "colourset.h"
+#include "textabbrev.h"
 
 #include <QPainter>
+#include <QFont>
 
 QRectF
 ConnectionItem::boundingRect() const
 {
-    if (!m_parent || !(m_child || m_uncommitted)) return QRectF();
+    if (!(m_child || m_uncommitted)) return QRectF();
     float xscale = 100;
     float yscale = 90;
     float size = 50;
 
-    int p_col = m_parent->column(), p_row = m_parent->row();
     int c_col, c_row;
     if (m_child) {
         c_col = m_child->column(); c_row = m_child->row();
@@ -40,6 +41,13 @@
         c_col = m_uncommitted->column(); c_row = m_uncommitted->row();
     }
 
+    int p_col, p_row;
+    if (m_parent) {
+        p_col = m_parent->column(); p_row = m_parent->row();
+    } else {
+        p_col = c_col - 1; p_row = c_row + 1;
+    }
+
     return QRectF(xscale * c_col + size/2 - 2,
 		  yscale * c_row + size - 22,
 		  xscale * p_col - xscale * c_col + 6,
@@ -50,17 +58,22 @@
 void
 ConnectionItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *, QWidget *)
 {
-    if (!m_parent || !(m_child || m_uncommitted)) return;
+    if (!(m_child || m_uncommitted)) return;
     QPainterPath p;
 
     paint->save();
 
+    int alpha = 255;
+    if (m_child && m_child->isClosed()) alpha = 90;
+
     ColourSet *colourSet = ColourSet::instance();
     QString branch;
     if (m_child) branch = m_child->getChangeset()->branch();
     else branch = m_uncommitted->branch();
     QColor branchColour = colourSet->getColourFor(branch);
 
+    branchColour.setAlpha(alpha);
+
     Qt::PenStyle ls = Qt::SolidLine;
     if (!m_child) ls = Qt::DashLine;
 
@@ -78,7 +91,6 @@
     float size = 50;
     float ygap = yscale - size - 2;
 
-    int p_col = m_parent->column(), p_row = m_parent->row();
     int c_col, c_row;
     if (m_child) {
         c_col = m_child->column(); c_row = m_child->row();
@@ -86,6 +98,13 @@
         c_col = m_uncommitted->column(); c_row = m_uncommitted->row();
     }
 
+    int p_col, p_row;
+    if (m_parent) {
+        p_col = m_parent->column(); p_row = m_parent->row();
+    } else {
+        p_col = c_col - 1; p_row = c_row + 1;
+    }
+
     float c_x = xscale * c_col + size/2;
     float p_x = xscale * p_col + size/2;
 
@@ -125,11 +144,42 @@
 	}
     }
 
-    // ensure line reaches the node -- again doesn't matter if we
-    // overshoot
-    p.lineTo(p_x, yscale * p_row + 20);
+    if (m_parent) {
 
+        // ensure line reaches the node -- again doesn't matter if we
+        // overshoot
+        p.lineTo(p_x, yscale * p_row + 20);
+
+    } else {
+
+        // no parent: merge from closed branch: draw only half the line
+        paint->setClipRect(QRectF((c_x + p_x)/2, yscale * c_row + size - 22,
+                                  xscale, yscale));
+    }
+    
     paint->drawPath(p);
+
+    if (!m_parent) {
+
+        // merge from closed branch: draw branch name
+
+        paint->setClipping(false);
+
+        QFont f;
+        f.setPixelSize(11);
+        f.setBold(true);
+        f.setItalic(false);
+	paint->setFont(f);
+
+	QString branch = m_mergedBranch;
+        if (branch == "") branch = "default";
+	int wid = xscale;
+	branch = TextAbbrev::abbreviate(branch, QFontMetrics(f), wid);
+	paint->drawText((c_x + p_x)/2 - wid - 2,
+                        yscale * c_row + size + ygap/2 + 2,
+                        branch);
+    }
+    
     paint->restore();
 }
 
--- a/src/connectionitem.h	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/connectionitem.h	Thu Nov 17 17:12:39 2011 +0000
@@ -50,12 +50,14 @@
     void setParent(ChangesetItem *p) { m_parent = p; }
     void setChild(ChangesetItem *c) { m_child = c; }
     void setChild(UncommittedItem *u) { m_uncommitted = u; }
+    void setMergedBranch(QString mb) { m_mergedBranch = mb; }
 
 private:
     Type m_type;
     ChangesetItem *m_parent;
     ChangesetItem *m_child;
     UncommittedItem *m_uncommitted;
+    QString m_mergedBranch;
 };
 
 #endif // CONNECTIONITEM_H
--- a/src/grapher.cpp	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/grapher.cpp	Thu Nov 17 17:12:39 2011 +0000
@@ -30,6 +30,7 @@
     QSettings settings;
     settings.beginGroup("Presentation");
     m_showDates = (settings.value("dateformat", 0) == 1);
+    m_showClosedBranches = (settings.value("showclosedbranches", false).toBool());
 }
 
 int Grapher::findAvailableColumn(int row, int parent, bool preferParentCol)
@@ -69,7 +70,7 @@
         throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
     }
     if (!m_items.contains(id)) {
-        throw LayoutException(QString("Changeset %1 not in item map").arg(id));
+        return;
     }
     Changeset *cs = m_changesets[id];
     ChangesetItem *item = m_items[id];
@@ -145,7 +146,7 @@
         throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
     }
     if (!m_items.contains(id)) {
-        throw LayoutException(QString("Changeset %1 not in item map").arg(id));
+        return;
     }
 
     Changeset *cs = m_changesets[id];
@@ -175,7 +176,7 @@
             !m_changesets[parentId]->isOnBranch(branch)) {
             // new branch
             col = m_branchHomes[branch];
-        } else {
+        } else if (m_items.contains(parentId)) {
             col = m_items[parentId]->column();
         }
 
@@ -191,9 +192,11 @@
         foreach (QString parentId, cs->parents()) {
             if (!m_changesets.contains(parentId)) continue;
             if (m_changesets[parentId]->isOnBranch(branch)) {
-                ChangesetItem *parentItem = m_items[parentId];
-                col += parentItem->column();
-                parentsOnSameBranch++;
+                if (m_items.contains(parentId)) {
+                    ChangesetItem *parentItem = m_items[parentId];
+                    col += parentItem->column();
+                    parentsOnSameBranch++;
+                }
             }
         }
 
@@ -244,7 +247,7 @@
 
     foreach (QString childId, cs->children()) {
         DEBUG << "reserving connection line space" << endl;
-        if (!m_changesets.contains(childId)) continue;
+        if (!m_items.contains(childId)) continue;
         Changeset *child = m_changesets[childId];
         int childRow = m_items[childId]->row();
         if (child->parents().size() > 1 ||
@@ -261,7 +264,7 @@
     if (nchildren > 1) {
         QList<QString> special;
         foreach (QString childId, cs->children()) {
-            if (!m_changesets.contains(childId)) continue;
+            if (!m_items.contains(childId)) continue;
             Changeset *child = m_changesets[childId];
             if (child->isOnBranch(branch) &&
                 child->parents().size() == 1) {
@@ -298,9 +301,10 @@
 void Grapher::allocateBranchHomes(Changesets csets)
 {
     foreach (Changeset *cs, csets) {
+        QString id = cs->id();
+        if (!m_items.contains(id)) continue;
+        ChangesetItem *item = m_items[id];
         QString branch = cs->branch();
-        ChangesetItem *item = m_items[cs->id()];
-        if (!item) continue;
         int row = item->row();
         if (!m_branchRanges.contains(branch)) {
             m_branchRanges[branch] = Range(row, row);
@@ -373,6 +377,105 @@
     return m_items[id];
 }
 
+void
+Grapher::markClosedChangesets()
+{
+    // Ensure the closed branch changesets are all marked as closed.
+
+    QSet<QString> deferred;
+
+    foreach (QString id, m_closedIds) {
+        markClosedChangesetsFrom(id, deferred);
+//        std::cerr << "after closed id " << id << ": candidates now contains " << deferred.size() << " element(s)" << std::endl;
+    }
+
+    while (!deferred.empty()) {
+        foreach (QString id, deferred) {
+            markClosedChangesetsFrom(id, deferred);
+            deferred.remove(id);
+//        std::cerr << "after id " << id << ": deferred now contains " << deferred.size() << " element(s)" << std::endl;
+        }
+    }
+}
+
+void
+Grapher::markClosedChangesetsFrom(QString id, QSet<QString> &deferred)
+{
+    // A changeset should be marked as closed (i) if it is in the list
+    // of closed heads [and has no children]; or (ii) all of its
+    // children that have the same branch name as it are marked as
+    // closed [and there is at least one of those]
+
+    if (!m_changesets.contains(id)) {
+//        std::cerr << "no good" << std::endl;
+        return;
+    }
+
+//    std::cerr << "looking at id " << id << std::endl;
+            
+    Changeset *cs = m_changesets[id];
+    QString branch = cs->branch();
+            
+    bool closed = false;
+
+    if (m_closedIds.contains(id)) {
+
+        closed = true;
+
+    } else {
+
+        closed = false;
+        foreach (QString childId, cs->children()) {
+            if (!m_changesets.contains(childId)) {
+                continue;
+            }
+            Changeset *ccs = m_changesets[childId];
+            if (ccs->isOnBranch(branch)) {
+                if (ccs->closed()) {
+                    // closed becomes true only when we see a closed
+                    // child on the same branch
+                    closed = true;
+                } else {
+                    // and it becomes false as soon as we see any
+                    // un-closed child on the same branch
+                    closed = false;
+                    break;
+                }
+            }
+        }
+    }
+
+    if (closed) {
+        // set closed on this cset and its direct simple parents
+        QString csid = id;
+        while (cs) {
+            cs->setClosed(true);
+            if (cs->parents().size() == 1) {
+                QString pid = cs->parents()[0];
+                if (!m_changesets.contains(pid)) break;
+                cs = m_changesets[pid];
+                if (cs->children().size() > 1) {
+//                    std::cerr << "adding pid " << pid << " (it has more than one child)" << std::endl;
+                    deferred.insert(pid); // examine later
+                    cs = 0;
+                }
+            } else if (cs->parents().size() > 1) {
+                foreach (QString pid, cs->parents()) {
+//                    std::cerr << "recursing to pid " << pid << " (it is one of multiple parents)" << std::endl;
+                    markClosedChangesetsFrom(pid, deferred);
+                }
+                cs = 0;
+            } else {
+                cs = 0;
+            }
+        }
+    } else {
+        cs->setClosed(false);
+    }
+    
+//    std::cerr << "finished with id " << id << std::endl;
+}
+
 void Grapher::layout(Changesets csets,
                      QStringList uncommittedParents,
                      QString uncommittedBranch)
@@ -391,7 +494,7 @@
 
     if (csets.empty()) return;
 
-    // Create (but don't yet position) the changeset items
+    // Initialise changesets hash
 
     foreach (Changeset *cs, csets) {
 
@@ -407,7 +510,28 @@
         }
 
         m_changesets[id] = cs;
+    }
+    
+    // Set the children for each changeset
 
+    foreach (Changeset *cs, csets) {
+        QString id = cs->id();
+        foreach (QString parentId, cs->parents()) {
+            if (!m_changesets.contains(parentId)) continue;
+            Changeset *parent = m_changesets[parentId];
+            parent->addChild(id);
+        }
+    }
+    
+    // Ensure the closed branch changesets are all marked as closed.
+
+    markClosedChangesets();
+
+    // Create (but don't yet position) the changeset items
+
+    foreach (Changeset *cs, csets) {
+        if (cs->closed() && !m_showClosedBranches) continue;
+        QString id = cs->id();
         ChangesetItem *item = new ChangesetItem(cs);
         item->setX(0);
         item->setY(0);
@@ -415,26 +539,36 @@
         m_items[id] = item;
         m_scene->addChangesetItem(item);
     }
+    
+    // Ensure the closing changeset items are appropriately marked
+
+    foreach (QString closedId, m_closedIds) {
+        if (!m_items.contains(closedId)) continue;
+        m_items[closedId]->setClosingCommit(true);
+    }
 
     // Add the connecting lines
 
     foreach (Changeset *cs, csets) {
         QString id = cs->id();
+        if (!m_items.contains(id)) continue;
         ChangesetItem *item = m_items[id];
         bool merge = (cs->parents().size() > 1);
         foreach (QString parentId, cs->parents()) {
             if (!m_changesets.contains(parentId)) continue;
-            Changeset *parent = m_changesets[parentId];
-            parent->addChild(id);
             ConnectionItem *conn = new ConnectionItem();
             if (merge) conn->setConnectionType(ConnectionItem::Merge);
             conn->setChild(item);
-            conn->setParent(m_items[parentId]);
             conn->setZValue(-1);
+            if (m_items.contains(parentId)) {
+                conn->setParent(m_items[parentId]);
+            } else {
+                conn->setMergedBranch(m_changesets[parentId]->branch());
+            }
             m_scene->addItem(conn);
         }
     }
-    
+
     // Add uncommitted item and connecting line as necessary
 
     if (!m_uncommittedParents.empty()) {
@@ -446,6 +580,7 @@
 
         bool haveParentOnBranch = false;
         foreach (QString p, m_uncommittedParents) {
+            if (!m_items.contains(p)) continue;
             ConnectionItem *conn = new ConnectionItem();
             conn->setConnectionType(ConnectionItem::Merge);
             ChangesetItem *pitem = m_items[p];
@@ -473,6 +608,7 @@
 
     foreach (Changeset *cs, csets) {
         QString id = cs->id();
+        if (!m_items.contains(id)) continue;
         ChangesetItem *item = m_items[id];
         bool haveChildOnSameBranch = false;
         foreach (QString childId, cs->children()) {
@@ -556,7 +692,9 @@
     // made double-width
 
     foreach (Changeset *cs, csets) {
-        ChangesetItem *item = m_items[cs->id()];
+        QString id = cs->id();
+        if (!m_items.contains(id)) continue;
+        ChangesetItem *item = m_items[id];
         if (isAvailable(item->row(), item->column()-1) &&
             isAvailable(item->row(), item->column()+1)) {
             item->setWide(true);
--- a/src/grapher.h	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/grapher.h	Thu Nov 17 17:12:39 2011 +0000
@@ -43,6 +43,8 @@
 
     UncommittedItem *getUncommittedItem() { return m_uncommitted; }
 
+    void setClosedHeadIds(QSet<QString> closed) { m_closedIds = closed; }
+
     class LayoutException : public std::exception {
     public:
 	LayoutException(QString message) throw() : m_message(message) { }
@@ -80,7 +82,10 @@
     typedef QMap<int, QString> RowDateMap;
     RowDateMap m_rowDates;
 
+    QSet<QString> m_closedIds;
+
     bool m_showDates;
+    bool m_showClosedBranches;
 
     QStringList m_uncommittedParents;
     int m_uncommittedParentRow;
@@ -93,6 +98,8 @@
     bool rangesConflict(const Range &r1, const Range &r2);
     int findAvailableColumn(int row, int parent, bool preferParentCol);
     bool isAvailable(int row, int col);
+    void markClosedChangesets();
+    void markClosedChangesetsFrom(QString id, QSet<QString> &deferred);
 };
 
 #endif 
--- a/src/hgaction.h	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/hgaction.h	Thu Nov 17 17:12:39 2011 +0000
@@ -31,6 +31,7 @@
     ACT_STAT,
     ACT_RESOLVE_LIST,
     ACT_QUERY_HEADS,
+    ACT_QUERY_HEADS_ACTIVE,
     ACT_QUERY_PARENTS,
     ACT_LOG,
     ACT_LOG_INCREMENTAL,
@@ -42,6 +43,7 @@
     ACT_CLONEFROMREMOTE,
     ACT_INIT,
     ACT_COMMIT,
+    ACT_CLOSE_BRANCH,
     ACT_ANNOTATE,
     ACT_UNCOMMITTED_SUMMARY,
     ACT_DIFF_SUMMARY,
@@ -92,6 +94,7 @@
         case ACT_STAT:
         case ACT_RESOLVE_LIST:
         case ACT_QUERY_HEADS:
+        case ACT_QUERY_HEADS_ACTIVE:
         case ACT_QUERY_PARENTS:
         case ACT_LOG_INCREMENTAL:
             return true;
--- a/src/hgtabwidget.cpp	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/hgtabwidget.cpp	Thu Nov 17 17:12:39 2011 +0000
@@ -28,7 +28,8 @@
 
 HgTabWidget::HgTabWidget(QWidget *parent,
                          QString workFolderPath) :
-    QTabWidget(parent)
+    QTabWidget(parent),
+    m_haveMerge(false)
 {
     // Work tab
     m_fileStatusWidget = new FileStatusWidget;
@@ -121,6 +122,9 @@
     connect(m_historyWidget, SIGNAL(newBranch(QString)),
             this, SIGNAL(newBranch(QString)));
 
+    connect(m_historyWidget, SIGNAL(closeBranch(QString)),
+            this, SIGNAL(closeBranch(QString)));
+
     connect(m_historyWidget, SIGNAL(tag(QString)),
             this, SIGNAL(tag(QString)));
 }
@@ -132,8 +136,12 @@
 
 void HgTabWidget::setCurrent(QStringList ids, QString branch)
 {
-    bool showUncommitted = haveChangesToCommit();
-    m_historyWidget->setCurrent(ids, branch, showUncommitted);
+    m_historyWidget->setCurrent(ids, branch, haveChangesToCommit());
+}
+
+void HgTabWidget::setClosedHeadIds(QSet<QString> closed)
+{
+    m_historyWidget->setClosedHeadIds(closed);
 }
 
 void HgTabWidget::updateFileStates()
@@ -153,8 +161,8 @@
 
 bool HgTabWidget::canCommit() const
 {
-    if (!m_fileStatusWidget->haveChangesToCommit()) return false;
-    if (!m_fileStatusWidget->getAllUnresolvedFiles().empty()) return false;
+    if (!haveChangesToCommit()) return false;
+    if (!getAllUnresolvedFiles().empty()) return false;
     return true;
 }
 
@@ -162,8 +170,8 @@
 {
     // Not the same as canCommit() -- we can revert (and diff)
     // unresolved files, but we can't commit them
-    if (!m_fileStatusWidget->haveChangesToCommit() &&
-        m_fileStatusWidget->getAllUnresolvedFiles().empty()) return false;
+    if (!haveChangesToCommit() &&
+        getAllUnresolvedFiles().empty()) return false;
     return true;
 }
 
@@ -172,10 +180,10 @@
     // Permit this only when work tab is visible
     if (currentIndex() != 0) return false;
 
-    QStringList addable = m_fileStatusWidget->getSelectedAddableFiles();
+    QStringList addable = getSelectedAddableFiles();
     if (addable.empty()) return false;
 
-    QStringList removable = m_fileStatusWidget->getSelectedRemovableFiles();
+    QStringList removable = getSelectedRemovableFiles();
     if (!removable.empty()) return false;
 
     return true;
@@ -186,14 +194,14 @@
     // Permit this only when work tab is visible
     if (currentIndex() != 0) return false;
 
-    if (m_fileStatusWidget->getSelectedRemovableFiles().empty()) return false;
-    if (!m_fileStatusWidget->getSelectedAddableFiles().empty()) return false;
+    if (getSelectedRemovableFiles().empty()) return false;
+    if (!getSelectedAddableFiles().empty()) return false;
     return true;
 }
 
 bool HgTabWidget::canResolve() const
 {
-    return !m_fileStatusWidget->getAllUnresolvedFiles().empty();
+    return !getAllUnresolvedFiles().empty();
 }
 
 bool HgTabWidget::canIgnore() const
@@ -203,7 +211,7 @@
 
 bool HgTabWidget::haveChangesToCommit() const
 {
-    return m_fileStatusWidget->haveChangesToCommit();
+    return m_haveMerge || m_fileStatusWidget->haveChangesToCommit();
 }
 
 QStringList HgTabWidget::getAllCommittableFiles() const
@@ -237,6 +245,15 @@
     m_fileStatusWidget->setFileStates(m_fileStates);
 }
 
+void HgTabWidget::setHaveMerge(bool haveMerge)
+{
+    if (m_haveMerge != haveMerge) {
+        m_haveMerge = haveMerge;
+        m_historyWidget->setShowUncommitted(haveChangesToCommit());
+        updateHistory();
+    }
+}
+
 void HgTabWidget::setNewLog(QString hgLogList)
 {
     m_historyWidget->parseNewLog(hgLogList);
--- a/src/hgtabwidget.h	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/hgtabwidget.h	Thu Nov 17 17:12:39 2011 +0000
@@ -48,6 +48,9 @@
     void setLocalPath(QString workFolderPath);
 
     void setCurrent(QStringList ids, QString branch);
+    void setClosedHeadIds(QSet<QString> ids);
+
+    void setHaveMerge(bool);
 
     void updateFileStates();
     void updateHistory();
@@ -89,6 +92,7 @@
     void diffToCurrent(QString id);
     void mergeFrom(QString id);
     void newBranch(QString id);
+    void closeBranch(QString id);
     void tag(QString id);
 
     void annotateFiles(QStringList);
@@ -113,6 +117,7 @@
     FileStatusWidget *m_fileStatusWidget;
     HistoryWidget *m_historyWidget;
     FileStates m_fileStates;
+    bool m_haveMerge;
 
     Changesets parseChangeSets(QString changeSetsStr);
 };
--- a/src/historywidget.cpp	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/historywidget.cpp	Thu Nov 17 17:12:39 2011 +0000
@@ -27,6 +27,7 @@
 #include <iostream>
 
 #include <QGridLayout>
+#include <QSettings>
 
 HistoryWidget::HistoryWidget() :
     m_showUncommitted(false),
@@ -39,12 +40,28 @@
     m_panned->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
     m_panned->setCacheMode(QGraphicsView::CacheNone);
 
+    int row = 0;
+
     QGridLayout *layout = new QGridLayout;
-    layout->addWidget(m_panned, 0, 0);
-    layout->addWidget(m_panner, 0, 1);
+    layout->setMargin(10);
+    layout->addWidget(m_panned, row, 0);
+    layout->addWidget(m_panner, row, 1);
     m_panner->setMaximumWidth(80);
     m_panner->connectToPanned(m_panned);
 
+    layout->setRowStretch(row, 20);
+
+    QSettings settings;
+    settings.beginGroup("Presentation");
+    bool showClosed = (settings.value("showclosedbranches", false).toBool());
+
+    m_showClosedBranches = new QCheckBox(tr("Show closed branches"), this);
+    m_showClosedBranches->setChecked(showClosed);
+    connect(m_showClosedBranches, SIGNAL(toggled(bool)), 
+            this, SLOT(showClosedChanged(bool)));
+    layout->addWidget(m_showClosedBranches, ++row, 0, Qt::AlignLeft);
+    m_showClosedBranches->hide();
+
     setLayout(layout);
 }
 
@@ -86,6 +103,27 @@
 
     m_refreshNeeded = true;
 }
+
+void HistoryWidget::setClosedHeadIds(QSet<QString> closed)
+{
+    if (closed == m_closedIds) return;
+    m_closedIds = closed;
+    m_showClosedBranches->setVisible(!closed.empty());
+    m_refreshNeeded = true;
+}
+
+void HistoryWidget::setShowUncommitted(bool showUncommitted)
+{
+    setCurrent(m_currentIds, m_currentBranch, showUncommitted);
+}
+
+void HistoryWidget::showClosedChanged(bool show)
+{
+    QSettings settings;
+    settings.beginGroup("Presentation");
+    settings.setValue("showclosedbranches", show);
+    layoutAll();
+}
     
 void HistoryWidget::parseNewLog(QString log)
 {
@@ -176,6 +214,7 @@
 
     if (!m_changesets.empty()) {
 	Grapher g(scene);
+        g.setClosedHeadIds(m_closedIds);
 	try {
 	    g.layout(m_changesets,
                      m_showUncommitted ? m_currentIds : QStringList(),
@@ -246,7 +285,8 @@
             DEBUG << "id " << id << " is new" << endl;
         }
 
-        if (csit->isCurrent() != current || csit->isNew() != newid) {
+        if (csit->isCurrent() != current ||
+            csit->isNew() != newid) {
             csit->setCurrent(current);
             csit->setNew(newid);
             csit->update();
@@ -298,6 +338,9 @@
     connect(scene, SIGNAL(newBranch(QString)),
             this, SIGNAL(newBranch(QString)));
 
+    connect(scene, SIGNAL(closeBranch(QString)),
+            this, SIGNAL(closeBranch(QString)));
+
     connect(scene, SIGNAL(tag(QString)),
             this, SIGNAL(tag(QString)));
 }
--- a/src/historywidget.h	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/historywidget.h	Thu Nov 17 17:12:39 2011 +0000
@@ -22,6 +22,7 @@
 
 #include <QWidget>
 #include <QSet>
+#include <QCheckBox>
 
 class Panned;
 class Panner;
@@ -37,6 +38,8 @@
     virtual ~HistoryWidget();
 
     void setCurrent(QStringList ids, QString branch, bool showUncommitted);
+    void setShowUncommitted(bool showUncommitted);
+    void setClosedHeadIds(QSet<QString> closed);
 
     void parseNewLog(QString log);
     void parseIncrementalLog(QString log);
@@ -60,18 +63,24 @@
     void diffToCurrent(QString id);
     void mergeFrom(QString id);
     void newBranch(QString id);
+    void closeBranch(QString id);
     void tag(QString id);
+
+private slots:
+    void showClosedChanged(bool);
     
 private:
     Changesets m_changesets;
     QStringList m_currentIds;
     QString m_currentBranch;
     QSet<QString> m_newIds;
+    QSet<QString> m_closedIds;
     bool m_showUncommitted;
     bool m_refreshNeeded;
 
     Panned *m_panned;
     Panner *m_panner;
+    QCheckBox *m_showClosedBranches;
 
     QGraphicsScene *scene();
     void clearChangesets();
--- a/src/main.cpp	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/main.cpp	Thu Nov 17 17:12:39 2011 +0000
@@ -44,6 +44,7 @@
 
     QTranslator translator;
     QString language = QLocale::system().name();
+    if (language == "C") language = "en";
     QString trname = QString("easyhg_%1").arg(language);
     translator.load(trname, ":");
     app.installTranslator(&translator);
--- a/src/mainwindow.cpp	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/mainwindow.cpp	Thu Nov 17 17:12:39 2011 +0000
@@ -35,6 +35,7 @@
 #include <QUrl>
 #include <QDialogButtonBox>
 #include <QTimer>
+#include <QTextBrowser>
 
 #include "mainwindow.h"
 #include "multichoicedialog.h"
@@ -55,6 +56,7 @@
 
 MainWindow::MainWindow(QString myDirPath) :
     m_myDirPath(myDirPath),
+    m_helpDialog(0),
     m_fsWatcherGeneralTimer(0),
     m_fsWatcherRestoreTimer(0),
     m_fsWatcherSuspended(false)
@@ -133,7 +135,7 @@
     cs->addDefaultName(getUserInfo());
 
     VersionTester *vt = new VersionTester
-        ("easymercurial.org", "/latest-version.txt", EASYHG_VERSION);
+        ("easyhg.org", "/latest-version.txt", EASYHG_VERSION);
     connect(vt, SIGNAL(newerVersionAvailable(QString)),
             this, SLOT(newerVersionAvailable(QString)));
 
@@ -321,6 +323,13 @@
 */
 }
 
+void MainWindow::hgQueryHeadsActive()
+{
+    QStringList params;
+    params << "heads";
+    m_runner->requestAction(HgAction(ACT_QUERY_HEADS_ACTIVE, m_workFolderPath, params));
+}
+
 void MainWindow::hgQueryHeads()
 {
     QStringList params;
@@ -534,6 +543,41 @@
     m_runner->requestAction(HgAction(ACT_NEW_BRANCH, m_workFolderPath, params));
 }
 
+void MainWindow::hgCloseBranch()
+{
+    QStringList params;
+
+    //!!! how to ensure this doesn't happen when uncommitted changes present?
+
+    QString cf(tr("Close branch"));
+    QString comment;
+
+    QString defaultWarning;
+
+    QString branchText;
+    if (m_currentBranch == "" || m_currentBranch == "default") {
+        branchText = tr("the default branch");
+        defaultWarning = tr("<p><b>Warning:</b> you are asking to close the default branch. This is not usually a good idea!</p>");
+    } else {
+        branchText = tr("branch \"%1\"").arg(m_currentBranch);
+    }
+
+    if (ConfirmCommentDialog::confirmAndGetLongComment
+        (this,
+         cf,
+         tr("<h3>%1</h3><p>%2%3").arg(cf)
+         .arg(tr("You are about to close %1.<p>This branch will be marked as closed and hidden from the history view.<p>You will still be able to see if it you select \"Show closed branches\" in the history view, and it will be reopened if you commit to it.<p>Please enter your comment for the commit log:").arg(branchText))
+         .arg(defaultWarning),
+         comment,
+         tr("C&lose branch"))) {
+
+        params << "commit" << "--message" << comment
+               << "--user" << getUserInfo() << "--close-branch";
+        
+        m_runner->requestAction(HgAction(ACT_CLOSE_BRANCH, m_workFolderPath, params));
+    }
+}
+
 void MainWindow::hgTag(QString id)
 {
     QStringList params;
@@ -1249,6 +1293,7 @@
     m_currentParents.clear();
     foreach (Changeset *cs, m_currentHeads) delete cs;
     m_currentHeads.clear();
+    m_closedHeadIds.clear();
     m_currentBranch = "";
     m_lastStatOutput = "";
     m_lastRevertedFiles.clear();
@@ -1977,18 +2022,18 @@
     bool headsAreLocal = false;
 
     if (m_currentParents.size() == 1) {
-        int m_currentBranchHeads = 0;
+        int currentBranchActiveHeads = 0;
         bool parentIsHead = false;
         Changeset *parent = m_currentParents[0];
-        foreach (Changeset *head, m_currentHeads) {
+        foreach (Changeset *head, m_activeHeads) {
             if (head->isOnBranch(m_currentBranch)) {
-                ++m_currentBranchHeads;
+                ++currentBranchActiveHeads;
             }
             if (parent->id() == head->id()) {
                 parentIsHead = true;
             }
         }
-        if (m_currentBranchHeads == 2 && parentIsHead) {
+        if (currentBranchActiveHeads == 2 && parentIsHead) {
             headsAreLocal = true;
         }
     }
@@ -2136,6 +2181,7 @@
             return;
         }
         break; // go on to default report
+    case ACT_QUERY_HEADS_ACTIVE:
     case ACT_QUERY_HEADS:
         // fails if repo is empty; we don't care (if there's a genuine
         // problem, something else will fail too).  Pretend it
@@ -2181,11 +2227,15 @@
 
 void MainWindow::commandCompleted(HgAction completedAction, QString output)
 {
+//    std::cerr << "commandCompleted: " << completedAction.action << std::endl;
+
     restoreFileSystemWatcher();
     HGACTIONS action = completedAction.action;
 
     if (action == ACT_NONE) return;
 
+    output.replace("\r\n", "\n");
+
     bool headsChanged = false;
     QStringList oldHeadIds;
 
@@ -2242,6 +2292,7 @@
         break;
 
     case ACT_RESOLVE_LIST:
+        // This happens on every update, after the stat (above)
         if (output != "") {
             // Remove lines beginning with R (they are resolved,
             // and the file stat parser treats R as removed)
@@ -2321,6 +2372,11 @@
     }
         break;
         
+    case ACT_QUERY_HEADS_ACTIVE:
+        foreach (Changeset *cs, m_activeHeads) delete cs;
+        m_activeHeads = Changeset::parseChangesets(output);
+        break;
+
     case ACT_QUERY_HEADS:
     {
         oldHeadIds = Changeset::getIds(m_currentHeads);
@@ -2333,6 +2389,7 @@
             headsChanged = true;
             foreach (Changeset *cs, m_currentHeads) delete cs;
             m_currentHeads = newHeads;
+            updateClosedHeads();
         }
     }
         break;
@@ -2347,6 +2404,12 @@
         m_shouldHgStat = true;
         break;
 
+    case ACT_CLOSE_BRANCH:
+        m_hgTabs->clearSelections();
+        m_justMerged = false;
+        m_shouldHgStat = true;
+        break;
+
     case ACT_REVERT:
         hgMarkFilesResolved(m_lastRevertedFiles);
         m_justMerged = false;
@@ -2377,7 +2440,6 @@
     case ACT_DIFF_SUMMARY:
     {
         // Output has log info first, diff following after a blank line
-        output.replace("\r\n", "\n");
         QStringList olist = output.split("\n\n", QString::SkipEmptyParts);
         if (olist.size() > 1) output = olist[1];
 
@@ -2440,7 +2502,8 @@
     //     incremental-log (only if heads changed) -> parents
     // 
     // Sequence when full log required:
-    //   paths -> branch -> stat -> resolve-list -> heads -> parents -> log
+    //   paths -> branch -> stat -> resolve-list -> heads ->
+    //     parents -> log
     //
     // Note we want to call enableDisableActions only once, at the end
     // of whichever sequence is in use.
@@ -2471,18 +2534,24 @@
         break;
         
     case ACT_QUERY_PATHS:
+        // NB this call is duplicated in hgQueryPaths
         hgQueryBranch();
         break;
 
     case ACT_QUERY_BRANCH:
+        // NB this call is duplicated in hgQueryBranch
         hgStat();
         break;
         
     case ACT_STAT:
         hgResolveList();
         break;
-
+        
     case ACT_RESOLVE_LIST:
+        hgQueryHeadsActive();
+        break;
+
+    case ACT_QUERY_HEADS_ACTIVE:
         hgQueryHeads();
         break;
 
@@ -2534,6 +2603,7 @@
 {
     connect(m_exitAct, SIGNAL(triggered()), this, SLOT(close()));
     connect(m_aboutAct, SIGNAL(triggered()), this, SLOT(about()));
+    connect(m_helpAct, SIGNAL(triggered()), this, SLOT(help()));
 
     connect(m_hgRefreshAct, SIGNAL(triggered()), this, SLOT(hgRefresh()));
     connect(m_hgRemoveAct, SIGNAL(triggered()), this, SLOT(hgRemove()));
@@ -2598,6 +2668,9 @@
     connect(m_hgTabs, SIGNAL(newBranch(QString)),
             this, SLOT(hgNewBranch()));
 
+    connect(m_hgTabs, SIGNAL(closeBranch(QString)),
+            this, SLOT(hgCloseBranch()));
+
     connect(m_hgTabs, SIGNAL(tag(QString)),
             this, SLOT(hgTag(QString)));
 
@@ -2655,8 +2728,6 @@
 
     QDir localRepoDir;
     QDir workFolderDir;
-    bool workFolderExist = true;
-    bool localRepoExist = true;
 
     m_remoteRepoActionsEnabled = true;
     if (m_remoteRepoPath.isEmpty()) {
@@ -2666,19 +2737,14 @@
     m_localRepoActionsEnabled = true;
     if (m_workFolderPath.isEmpty()) {
         m_localRepoActionsEnabled = false;
-        workFolderExist = false;
     }
 
     if (m_workFolderPath == "" || !workFolderDir.exists(m_workFolderPath)) {
         m_localRepoActionsEnabled = false;
-        workFolderExist = false;
-    } else {
-        workFolderExist = true;
     }
 
     if (!localRepoDir.exists(m_workFolderPath + "/.hg")) {
         m_localRepoActionsEnabled = false;
-        localRepoExist = false;
     }
 
     bool haveDiff = false;
@@ -2689,6 +2755,8 @@
     }
     settings.endGroup();
 
+    m_hgTabs->setHaveMerge(m_currentParents.size() == 2);
+
     m_hgRefreshAct->setEnabled(m_localRepoActionsEnabled);
     m_hgFolderDiffAct->setEnabled(m_localRepoActionsEnabled && haveDiff);
     m_hgRevertAct->setEnabled(m_localRepoActionsEnabled);
@@ -2726,24 +2794,32 @@
     bool emptyRepo = false;
     bool noWorkingCopy = false;
     bool newBranch = false;
-    int m_currentBranchHeads = 0;
+    bool closedBranch = false;
+    int currentBranchActiveHeads = 0;
 
     if (m_currentParents.size() == 1) {
         bool parentIsHead = false;
+        bool parentIsActiveHead = false;
         Changeset *parent = m_currentParents[0];
-        foreach (Changeset *head, m_currentHeads) {
-            DEBUG << "head branch " << head->branch() << ", current branch " << m_currentBranch << endl;
+        foreach (Changeset *head, m_activeHeads) {
             if (head->isOnBranch(m_currentBranch)) {
-                ++m_currentBranchHeads;
+                ++currentBranchActiveHeads;
             }
             if (parent->id() == head->id()) {
-                parentIsHead = true;
+                parentIsActiveHead = parentIsHead = true;
             }
         }
-        if (m_currentBranchHeads == 2 && parentIsHead) {
+        if (!parentIsActiveHead) {
+            foreach (Changeset *head, m_currentHeads) {
+                if (parent->id() == head->id()) {
+                    parentIsHead = true;
+                }
+            }
+        }
+        if (currentBranchActiveHeads == 2 && parentIsActiveHead) {
             canMerge = true;
         }
-        if (m_currentBranchHeads == 0 && parentIsHead) {
+        if (currentBranchActiveHeads == 0 && parentIsActiveHead) {
             // Just created a new branch
             newBranch = true;
         }
@@ -2754,6 +2830,8 @@
             foreach (Changeset *h, m_currentHeads) {
                 DEBUG << "head id = " << h->id() << endl;
             }
+        } else if (!parentIsActiveHead) {
+            closedBranch = true;
         }
         m_justMerged = false;
     } else if (m_currentParents.size() == 0) {
@@ -2772,16 +2850,16 @@
         haveMerge = true;
         m_justMerged = true;
     }
-        
+
     m_hgIncomingAct->setEnabled(m_remoteRepoActionsEnabled);
     m_hgPullAct->setEnabled(m_remoteRepoActionsEnabled);
     // permit push even if no remote yet; we'll ask for one
     m_hgPushAct->setEnabled(m_localRepoActionsEnabled && !emptyRepo);
 
     m_hgMergeAct->setEnabled(m_localRepoActionsEnabled &&
-                           (canMerge || m_hgTabs->canResolve()));
+                             (canMerge || m_hgTabs->canResolve()));
     m_hgUpdateAct->setEnabled(m_localRepoActionsEnabled &&
-                            (canUpdate && !m_hgTabs->haveChangesToCommit()));
+                              (canUpdate && !m_hgTabs->haveChangesToCommit()));
 
     // Set the state field on the file status widget
 
@@ -2810,6 +2888,12 @@
         m_workStatus->setState(tr("Have merged but not yet committed on %1").arg(branchText));
     } else if (newBranch) {
         m_workStatus->setState(tr("On %1.  New branch: has not yet been committed").arg(branchText));
+    } else if (closedBranch) {
+        if (canUpdate) {
+            m_workStatus->setState(tr("On a closed branch. Not at the head of the branch"));
+        } else {
+            m_workStatus->setState(tr("At the head of a closed branch"));
+        }
     } else if (canUpdate) {
         if (m_hgTabs->haveChangesToCommit()) {
             // have uncommitted changes
@@ -2818,14 +2902,29 @@
             // no uncommitted changes
             m_workStatus->setState(tr("On %1. Not at the head of the branch: consider updating").arg(branchText));
         }
-    } else if (m_currentBranchHeads > 1) {
-        m_workStatus->setState(tr("At one of %n heads of %1", "", m_currentBranchHeads).arg(branchText));
+    } else if (currentBranchActiveHeads > 1) {
+        m_workStatus->setState(tr("At one of %n heads of %1", "", currentBranchActiveHeads).arg(branchText));
     } else {
         m_workStatus->setState(tr("At the head of %1").arg(branchText));
     }
 }
 
 
+void MainWindow::updateClosedHeads()
+{
+    m_closedHeadIds.clear();
+    QSet<QString> activeIds;
+    foreach (Changeset *cs, m_activeHeads) {
+        activeIds.insert(cs->id());
+    }
+    foreach (Changeset *cs, m_currentHeads) {
+        if (!activeIds.contains(cs->id())) {
+            m_closedHeadIds.insert(cs->id());
+        }
+    }
+    m_hgTabs->setClosedHeadIds(m_closedHeadIds);
+}
+
 void MainWindow::updateRecentMenu()
 {
     m_recentMenu->clear();
@@ -2925,6 +3024,12 @@
     m_hgServeAct->setStatusTip(tr("Serve local repository temporarily via HTTP for workgroup access"));
 
     //Help actions
+#ifdef Q_OS_MAC
+    m_helpAct = new QAction(tr("EasyMercurial Help"), this);
+#else
+    m_helpAct = new QAction(tr("Help Topics"), this);
+#endif
+    m_helpAct->setShortcuts(QKeySequence::HelpContents);
     m_aboutAct = new QAction(tr("About EasyMercurial"), this);
 
     // Miscellaneous
@@ -2973,6 +3078,7 @@
     remoteMenu->addAction(m_hgPushAct);
 
     m_helpMenu = menuBar()->addMenu(tr("&Help"));
+    m_helpMenu->addAction(m_helpAct);
     m_helpMenu->addAction(m_aboutAct);
 }
 
@@ -3082,3 +3188,43 @@
     settings.endGroup();
 }
 
+void MainWindow::help()
+{
+    if (!m_helpDialog) {
+        m_helpDialog = new QDialog;
+        QGridLayout *layout = new QGridLayout;
+        m_helpDialog->setLayout(layout);
+        QPushButton *home = new QPushButton;
+        home->setIcon(QIcon(":images/home.png"));
+        layout->addWidget(home, 0, 0);
+        QPushButton *back = new QPushButton;
+        back->setIcon(QIcon(":images/back.png"));
+        layout->addWidget(back, 0, 1);
+        QPushButton *fwd = new QPushButton;
+        fwd->setIcon(QIcon(":images/forward.png"));
+        layout->addWidget(fwd, 0, 2);
+        QTextBrowser *text = new QTextBrowser;
+        text->setOpenExternalLinks(true);
+        layout->addWidget(text, 1, 0, 1, 4);
+        text->setSource(QUrl("qrc:help/topics.html"));
+        QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Close);
+        connect(bb, SIGNAL(rejected()), m_helpDialog, SLOT(hide()));
+        connect(text, SIGNAL(backwardAvailable(bool)),
+                back, SLOT(setEnabled(bool)));
+        connect(text, SIGNAL(forwardAvailable(bool)),
+                fwd, SLOT(setEnabled(bool)));
+        connect(home, SIGNAL(clicked()), text, SLOT(home()));
+        connect(back, SIGNAL(clicked()), text, SLOT(backward()));
+        connect(fwd, SIGNAL(clicked()), text, SLOT(forward()));
+        back->setEnabled(false);
+        fwd->setEnabled(false);
+        layout->addWidget(bb, 2, 0, 1, 4);
+        layout->setColumnStretch(3, 20);
+        m_helpDialog->resize(450, 500);
+    }
+    QTextBrowser *tb = m_helpDialog->findChild<QTextBrowser *>();
+    if (tb) tb->home();
+    m_helpDialog->show();
+    m_helpDialog->raise();
+}
+
--- a/src/mainwindow.h	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/mainwindow.h	Thu Nov 17 17:12:39 2011 +0000
@@ -57,6 +57,7 @@
 
 private slots:
     void about();
+    void help();
     void settings();
     void settings(SettingsDialog::Tab);
     void open();
@@ -94,6 +95,7 @@
     void hgTag(QString);
     void hgNewBranch();
     void hgNoBranch();
+    void hgCloseBranch();
     void hgServe();
     void hgIgnore();
     void hgEditIgnore();
@@ -120,6 +122,7 @@
 
 private:
     void hgQueryBranch();
+    void hgQueryHeadsActive();
     void hgQueryHeads();
     void hgQueryParents();
     void hgLog();
@@ -177,6 +180,8 @@
     void suspendFileSystemWatcher();
     void restoreFileSystemWatcher();
 
+    void updateClosedHeads();
+
     void updateWorkFolderAndRepoNames();
 
     WorkStatusWidget *m_workStatus;
@@ -186,6 +191,8 @@
     QString m_workFolderPath;
     QString m_currentBranch;
     Changesets m_currentHeads;
+    Changesets m_activeHeads;
+    QSet<QString> m_closedHeadIds;
     Changesets m_currentParents;
     int m_commitsSincePush;
     bool m_stateUnknown;
@@ -234,11 +241,14 @@
 
     // Help menu actions
     QAction *m_aboutAct;
+    QAction *m_helpAct;
 
     QToolBar *m_fileToolBar;
     QToolBar *m_repoToolBar;
     QToolBar *m_workFolderToolBar;
 
+    QDialog *m_helpDialog;
+
     HgRunner *m_runner;
 
     bool m_shouldHgStat;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/squeezedlabel.cpp	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,180 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2011 Chris Cannam
+    Copyright (c) 2011 Queen Mary, University of London
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+/*
+    This file adapted from Rosegarden, a sequencer and musical
+    notation editor.  Copyright 2000-2011 the Rosegarden development
+    team.
+
+    Adapted from KDE 4.2.0, this code originally Copyright (c) 2000
+    Ronny Standtke.
+*/
+
+#include "squeezedlabel.h"
+
+#include <iostream>
+
+#include <QContextMenuEvent>
+#include <QAction>
+#include <QMenu>
+#include <QClipboard>
+#include <QApplication>
+#include <QMimeData>
+#include <QDesktopWidget>
+
+
+class SqueezedLabelPrivate
+{
+public:
+};
+
+void SqueezedLabel::_k_copyFullText()
+{
+    QMimeData* data = new QMimeData;
+    data->setText(fullText);
+    QApplication::clipboard()->setMimeData(data);
+}
+
+SqueezedLabel::SqueezedLabel(const QString &text , QWidget *parent)
+        : QLabel (parent)
+{
+    setObjectName("SQUEEZED");
+    setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
+    fullText = text;
+    elideMode = Qt::ElideMiddle;
+    squeezeTextToLabel();
+}
+
+SqueezedLabel::SqueezedLabel(QWidget *parent)
+        : QLabel (parent)
+{
+    setObjectName("SQUEEZED");
+    setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
+    elideMode = Qt::ElideMiddle;
+}
+
+SqueezedLabel::~SqueezedLabel()
+{
+}
+
+void SqueezedLabel::resizeEvent(QResizeEvent *)
+{
+    squeezeTextToLabel();
+}
+
+QSize SqueezedLabel::minimumSizeHint() const
+{
+    QSize sh = QLabel::minimumSizeHint();
+    sh.setWidth(-1);
+    return sh;
+}
+
+QSize SqueezedLabel::sizeHint() const
+{
+    int dw = QApplication::desktop()->availableGeometry(QPoint(0, 0)).width();
+    int maxWidth = dw * 3 / 4;
+    QFontMetrics fm(fontMetrics());
+    int textWidth = fm.width(fullText);
+    if (textWidth > maxWidth) {
+        textWidth = maxWidth;
+    }
+    return QSize(textWidth, QLabel::sizeHint().height());
+}
+
+void SqueezedLabel::setText(const QString &text)
+{
+    fullText = text;
+    squeezeTextToLabel();
+}
+
+void SqueezedLabel::clear() {
+    fullText.clear();
+    QLabel::clear();
+}
+
+void SqueezedLabel::squeezeTextToLabel() {
+    QFontMetrics fm(fontMetrics());
+    int labelWidth = size().width();
+    QStringList squeezedLines;
+    bool squeezed = false;
+    Q_FOREACH(const QString& line, fullText.split('\n')) {
+        int lineWidth = fm.width(line);
+        if (lineWidth > labelWidth) {
+            squeezed = true;
+            squeezedLines << fm.elidedText(line, elideMode, labelWidth);
+        } else {
+            squeezedLines << line;
+        }
+    }
+
+    if (squeezed) {
+        QLabel::setText(squeezedLines.join("\n"));
+        setToolTip(fullText);
+    } else {
+        QLabel::setText(fullText);
+        setToolTip(QString());
+    }
+}
+
+void SqueezedLabel::setAlignment(Qt::Alignment alignment)
+{
+    // save fullText and restore it
+    QString tmpFull(fullText);
+    QLabel::setAlignment(alignment);
+    fullText = tmpFull;
+}
+
+Qt::TextElideMode SqueezedLabel::textElideMode() const
+{
+    return elideMode;
+}
+
+void SqueezedLabel::setTextElideMode(Qt::TextElideMode mode)
+{
+    elideMode = mode;
+    squeezeTextToLabel();
+}
+
+void SqueezedLabel::contextMenuEvent(QContextMenuEvent* ev)
+{
+    // "We" means the KDE team here.
+    //
+    // We want to reimplement "Copy" to include the elided text.
+    // But this means reimplementing the full popup menu, so no more
+    // copy-link-address or copy-selection support anymore, since we
+    // have no access to the QTextDocument.
+    // Maybe we should have a boolean flag in SqueezedLabel itself for
+    // whether to show the "Copy Full Text" custom popup?
+    // For now I chose to show it when the text is squeezed; when it's not, the
+    // standard popup menu can do the job (select all, copy).
+
+    const bool squeezed = text() != fullText;
+    const bool showCustomPopup = squeezed;
+    if (showCustomPopup) {
+        QMenu menu(this);
+
+        QAction* act = new QAction(tr("&Copy Full Text"), this);
+        connect(act, SIGNAL(triggered()), this, SLOT(_k_copyFullText()));
+        menu.addAction(act);
+
+        ev->accept();
+        menu.exec(ev->globalPos());
+    } else {
+        QLabel::contextMenuEvent(ev);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/squeezedlabel.h	Thu Nov 17 17:12:39 2011 +0000
@@ -0,0 +1,120 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2011 Chris Cannam
+    Copyright (c) 2011 Queen Mary, University of London
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+/*
+    This file adapted from Rosegarden, a sequencer and musical
+    notation editor.  Copyright 2000-2011 the Rosegarden development
+    team.
+
+    Adapted from KDE 4.2.0, this code originally Copyright (c) 2000
+    Ronny Standtke.
+*/
+
+#ifndef _SQUEEZED_LABEL_H_
+#define _SQUEEZED_LABEL_H_
+
+#include <QLabel>
+
+class SqueezedLabelPrivate;
+
+/**
+ * @short A replacement for QLabel that squeezes its text
+ *
+ * A label class that squeezes its text into the label
+ *
+ * If the text is too long to fit into the label it is divided into
+ * remaining left and right parts which are separated by three dots.
+ */
+
+class SqueezedLabel : public QLabel
+{
+    Q_OBJECT
+    Q_PROPERTY(Qt::TextElideMode textElideMode READ textElideMode WRITE setTextElideMode)
+
+public:
+    /**
+    * Default constructor.
+    */
+    explicit SqueezedLabel(QWidget *parent = 0);
+    explicit SqueezedLabel(const QString &text, QWidget *parent = 0);
+
+    virtual ~SqueezedLabel();
+
+    virtual QSize minimumSizeHint() const;
+    virtual QSize sizeHint() const;
+    /**
+    * Overridden for internal reasons; the API remains unaffected.
+    */
+    virtual void setAlignment(Qt::Alignment);
+
+    /**
+    *  Returns the text elide mode.
+    */
+    Qt::TextElideMode textElideMode() const;
+
+    /**
+    * Sets the text elide mode.
+    * @param mode The text elide mode.
+    */
+    void setTextElideMode(Qt::TextElideMode mode);
+
+public Q_SLOTS:
+    /**
+    * Sets the text. Note that this is not technically a reimplementation of QLabel::setText(),
+    * which is not virtual (in Qt 4.3). Therefore, you may need to cast the object to
+    * SqueezedLabel in some situations:
+    * \Example
+    * \code
+    * SqueezedLabel* squeezed = new SqueezedLabel("text", parent);
+    * QLabel* label = squeezed;
+    * label->setText("new text");    // this will not work
+    * squeezed->setText("new text");    // works as expected
+    * static_cast<SqueezedLabel*>(label)->setText("new text");    // works as expected
+    * \endcode
+    * @param mode The new text.
+    */
+    void setText(const QString &text);
+    /**
+    * Clears the text. Same remark as above.
+    *
+    */
+    void clear();
+
+protected:
+    /**
+    * Called when widget is resized
+    */
+    void resizeEvent(QResizeEvent *);
+    /**
+    * \reimp
+    */
+    void contextMenuEvent(QContextMenuEvent*);
+    /**
+    * does the dirty work
+    */
+    void squeezeTextToLabel();
+
+private slots:
+    void _k_copyFullText();
+
+private:
+    QString fullText;
+    Qt::TextElideMode elideMode;
+};
+
+
+#endif
--- a/src/version.h	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/version.h	Thu Nov 17 17:12:39 2011 +0000
@@ -1,1 +1,1 @@
-#define EASYHG_VERSION "1.0"
+#define EASYHG_VERSION "1.0.1"
--- a/src/workstatuswidget.cpp	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/workstatuswidget.cpp	Thu Nov 17 17:12:39 2011 +0000
@@ -18,6 +18,7 @@
 #include "workstatuswidget.h"
 #include "debug.h"
 #include "clickablelabel.h"
+#include "squeezedlabel.h"
 
 #include <QGridLayout>
 #include <QSpacerItem>
@@ -48,11 +49,11 @@
     m_openButton->setFont(f);
     m_openButton->setMouseUnderline(true);
     connect(m_openButton, SIGNAL(clicked()), this, SLOT(openButtonClicked()));
-    layout->addWidget(m_openButton, row, 2, 1, 2, Qt::AlignLeft);
+    layout->addWidget(m_openButton, row, 2, 1, 2);
 
     ++row;
     layout->addWidget(new QLabel(tr("Remote:")), row, 1);
-    m_remoteURLLabel = new QLabel;
+    m_remoteURLLabel = new SqueezedLabel;
     m_remoteURLLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
     layout->addWidget(m_remoteURLLabel, row, 2, 1, 2);
 
--- a/src/workstatuswidget.h	Thu Nov 17 16:40:48 2011 +0000
+++ b/src/workstatuswidget.h	Thu Nov 17 17:12:39 2011 +0000
@@ -20,6 +20,8 @@
 
 #include <QWidget>
 
+class SqueezedLabel;
+
 class QLabel;
 class QPushButton;
 class QFileInfo;
@@ -51,7 +53,7 @@
     ClickableLabel *m_openButton;
 
     QString m_remoteURL;
-    QLabel *m_remoteURLLabel;
+    SqueezedLabel *m_remoteURLLabel;
 
     QString m_state;
     QLabel *m_stateLabel;