{"thread":{"id":"6575","subject":"[PATCH 4/7] Allows extraction of information about remotes.","startedAt":"2007-01-29T23:05:19Z","lastAt":"2007-02-01T18:47:05Z","messageCount":10,"participants":["Yann Dirson","Catalin Marinas"],"isPatch":true,"patchVersion":1,"patchTotal":7},"messages":[{"id":"32989","messageId":"20070129230117.7102.64322.stgit@gandelf.nowhere.earth","threadId":"6575","inReplyTo":null,"subject":"[PATCH 0/7] StGIT, remotes, and parent","fromName":"Yann Dirson","fromEmail":"ydirson@altern.org","sentAt":"2007-01-29T23:05:19Z","receivedAt":"2007-01-29T23:05:19Z","isPatch":true,"sender":{"key":"ydirson@altern.org","avatar":"https://avatars.githubusercontent.com/u/1190950?v=4"},"body":"This new series should bring the first 5 patches into sufficiently\nreasonable shape, so I'm presenting them for inclusion.  The last 2\nones are still there for context only.\n\nThis new revision of the series brings:\n\n- Better use of stgit.config in subsequent patches, notably deported\nthe remaining call to repo-config into stgit.config:\nconfig.sections_matching(regexp)\n\n- Caching in stgit.config.  I implemented this as on-demand caching,\ninitially because it was straightforward, but it turns out that\nsections_matching() naturally fits in this model, and i'm not sure how\nwe could have implemented this in the pre-caching model.\n\n- The usual bunch of misc cleanups.\n\nBest regards,\n-- \nYann Dirson    <ydirson@altern.org> |\nDebian-related: <dirson@debian.org> |   Support Debian GNU/Linux:\n                                    |  Freedom, Power, Stability, Gratis\n     http://ydirson.free.fr/        | Check <http://www.debian.org/>\n"},{"id":"32987","messageId":"20070129230525.7102.2500.stgit@gandelf.nowhere.earth","threadId":"6575","inReplyTo":"20070129230117.7102.64322.stgit@gandelf.nowhere.earth","subject":"[PATCH 1/7] Make stgit.config use git-repo-config.","fromName":"Yann Dirson","fromEmail":"ydirson@altern.org","sentAt":"2007-01-29T23:05:25Z","receivedAt":"2007-01-29T23:05:25Z","isPatch":true,"sender":{"key":"ydirson@altern.org","avatar":"https://avatars.githubusercontent.com/u/1190950?v=4"},"body":"\nThis is an initial implementation without any sort of caching.  If we\nwant that, we could do it with this new API.  We could even just\nsimply generalize the use of ConfigOption instead.\n\nI just copypasted __run from stgit.git to avoid too much work, but\nit's definitely something to cleanup.\n\nSigned-off-by: Yann Dirson <ydirson@altern.org>\n---\n\n stgit/commands/common.py  |    5 +\n stgit/commands/mail.py    |   36 +++-------\n stgit/commands/pull.py    |    9 +-\n stgit/commands/refresh.py |    2 -\n stgit/config.py           |  166 +++++++++++++++++++++++----------------------\n stgit/git.py              |   10 +--\n stgit/gitmergeonefile.py  |    7 +-\n stgit/stack.py            |    5 +\n 8 files changed, 113 insertions(+), 127 deletions(-)\n\ndiff --git a/stgit/commands/common.py b/stgit/commands/common.py\nindex 2e57824..432edce 100644\n--- a/stgit/commands/common.py\n+++ b/stgit/commands/common.py\n@@ -311,9 +311,10 @@ def address_or_alias(addr_str):\n         if addr.find('@') >= 0:\n             # it's an e-mail address\n             return addr\n-        if config.has_option('mail \"alias\"', addr):\n+        alias = config.get('mail.alias.'+addr)\n+        if alias:\n             # it's an alias\n-            return config.get('mail \"alias\"', addr)\n+            return alias\n \n         raise CmdException, 'unknown e-mail alias: %s' % addr\n \ndiff --git a/stgit/commands/mail.py b/stgit/commands/mail.py\nindex 6f61b83..7f20f13 100644\n--- a/stgit/commands/mail.py\n+++ b/stgit/commands/mail.py\n@@ -129,9 +129,8 @@ def __get_sender():\n     \"\"\"Return the 'authname <authemail>' string as read from the\n     configuration file\n     \"\"\"\n-    if config.has_option('stgit', 'sender'):\n-        sender = config.get('stgit', 'sender')\n-    else:\n+    sender=config.get('stgit.sender')\n+    if not sender:\n         try:\n             sender = str(git.user())\n         except git.GitException:\n@@ -207,10 +206,7 @@ def __build_address_headers(msg, options, extra_cc = []):\n     cc_addr = ''\n     bcc_addr = ''\n \n-    if config.has_option('stgit', 'autobcc'):\n-        autobcc = config.get('stgit', 'autobcc')\n-    else:\n-        autobcc = ''\n+    autobcc = config.get('stgit.autobcc') or ''\n \n     if options.to:\n         to_addr = ', '.join(options.to)\n@@ -283,8 +279,9 @@ def __edit_message(msg):\n     f.close()\n \n     # the editor\n-    if config.has_option('stgit', 'editor'):\n-        editor = config.get('stgit', 'editor')\n+    editor = config.get('stgit.editor')\n+    if editor:\n+        pass\n     elif 'EDITOR' in os.environ:\n         editor = os.environ['EDITOR']\n     else:\n@@ -469,14 +466,7 @@ def func(parser, options, args):\n     \"\"\"Send the patches by e-mail using the patchmail.tmpl file as\n     a template\n     \"\"\"\n-    smtpserver = config.get('stgit', 'smtpserver')\n-\n-    smtpuser = None\n-    smtppassword = None\n-    if config.has_option('stgit', 'smtpuser'):\n-        smtpuser = config.get('stgit', 'smtpuser')\n-    if config.has_option('stgit', 'smtppassword'):\n-        smtppassword = config.get('stgit', 'smtppassword')\n+    smtpserver = config.get('stgit.smtpserver')\n \n     applied = crt_series.get_applied()\n \n@@ -488,11 +478,8 @@ def func(parser, options, args):\n     else:\n         raise CmdException, 'Incorrect options. Unknown patches to send'\n \n-    if options.smtp_password:\n-        smtppassword = options.smtp_password\n-\n-    if options.smtp_user:\n-        smtpuser = options.smtp_user\n+    smtppassword = options.smtp_password or config.get('stgit.smtppassword')\n+    smtpuser = options.smtp_user or config.get('stgit.smtpuser')\n \n     if (smtppassword and not smtpuser):\n         raise CmdException, 'SMTP password supplied, username needed'\n@@ -508,10 +495,7 @@ def func(parser, options, args):\n     else:\n         ref_id = options.refid\n \n-    if options.sleep != None:\n-        sleep = options.sleep\n-    else:\n-        sleep = config.getint('stgit', 'smtpdelay')\n+    sleep = options.sleep or config.getint('stgit.smtpdelay')\n \n     # send the cover message (if any)\n     if options.cover or options.edit_cover:\ndiff --git a/stgit/commands/pull.py b/stgit/commands/pull.py\nindex 7c5db22..e47858a 100644\n--- a/stgit/commands/pull.py\n+++ b/stgit/commands/pull.py\n@@ -53,11 +53,7 @@ def func(parser, options, args):\n     if len(args) >= 1:\n         repository = args[0]\n     else:\n-        section = 'branch \"%s\"' % git.get_head_file()\n-        if config.has_option(section, 'remote'):\n-            repository = config.get(section, 'remote')\n-        else:\n-            repository = 'origin'\n+        repository = config.get('branch.%s.remote' % git.get_head_file()) or 'origin'\n \n     refspec = None\n     if len(args) == 2:\n@@ -88,8 +84,7 @@ def func(parser, options, args):\n         push_patches(applied, options.merged)\n \n     # maybe tidy up\n-    repack = config.get('stgit', 'keepoptimized')\n-    if repack == 'yes':\n+    if config.get('stgit.keepoptimized') == 'yes':\n         git.repack()\n \n     print_crt_patch()\ndiff --git a/stgit/commands/refresh.py b/stgit/commands/refresh.py\nindex 0efe1df..7a9bf7d 100644\n--- a/stgit/commands/refresh.py\n+++ b/stgit/commands/refresh.py\n@@ -77,7 +77,7 @@ options = [make_option('-f', '--force',\n \n \n def func(parser, options, args):\n-    autoresolved = config.get('stgit', 'autoresolved')\n+    autoresolved = config.get('stgit.autoresolved')\n \n     if autoresolved != 'yes':\n         check_conflicts()\ndiff --git a/stgit/config.py b/stgit/config.py\nindex a6afbfd..d42754b 100644\n--- a/stgit/config.py\n+++ b/stgit/config.py\n@@ -18,91 +18,97 @@ along with this program; if not, write to the Free Software\n Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n \"\"\"\n \n-import os, ConfigParser\n-from StringIO import StringIO\n+import os, re\n from stgit import basedir\n \n-config = ConfigParser.RawConfigParser()\n-\n-def git_config(filename):\n-    \"\"\"Open a git config file and convert it to be understood by\n-    Python.\"\"\"\n-    try:\n-        f = file(filename)\n-        cont = False\n-        lines = []\n-        for line in f:\n-            line = line.strip()\n-\n-            if cont:\n-                # continued line, add a space at the beginning\n-                line = ' ' + line\n-\n-            if line and line[-1] == '\\\\':\n-                line = line[:-1].rstrip()\n-                cont = True\n-            else:\n-                line = line + '\\n'\n-                cont = False\n-\n-            lines.append(line)\n-\n-        f.close()\n-        cfg_str = ''.join(lines)\n-    except IOError:\n-        cfg_str = ''\n-\n-    strio = StringIO(cfg_str)\n-    strio.name = filename\n-\n-    return strio\n+class GitConfigException(Exception):\n+    pass\n+\n+class GitConfig:\n+    __defaults={\n+        'stgit.autoresolved':\t'no',\n+        'stgit.smtpserver':\t'localhost:25',\n+        'stgit.smtpdelay':\t'5',\n+        'stgit.pullcmd':\t'git-pull',\n+        'stgit.merger':\t\t'diff3 -L current -L ancestor -L patched -m -E ' \\\n+\t\t\t\t'\"%(branch1)s\" \"%(ancestor)s\" \"%(branch2)s\" > \"%(output)s\"',\n+        'stgit.autoimerge':\t'no',\n+        'stgit.keeporig':\t'yes',\n+        'stgit.keepoptimized':\t'no',\n+        'stgit.extensions':\t'.ancestor .current .patched',\n+        'stgit.shortnr':\t '5'\n+        }\n+\n+    def __run(self, cmd, args=None):\n+        \"\"\"__run: runs cmd using spawnvp.\n+    \n+        Runs cmd using spawnvp.  The shell is avoided so it won't mess up\n+        our arguments.  If args is very large, the command is run multiple\n+        times; args is split xargs style: cmd is passed on each\n+        invocation.  Unlike xargs, returns immediately if any non-zero\n+        return code is received.  \n+        \"\"\"\n+        \n+        args_l=cmd.split()\n+        if args is None:\n+            args = []\n+        for i in range(0, len(args)+1, 100):\n+            r=os.spawnvp(os.P_WAIT, args_l[0], args_l + args[i:min(i+100, len(args))])\n+        if r:\n+            return r\n+        return 0\n+    \n+    def get(self, name):\n+        stream = os.popen('git repo-config --get %s' % name, 'r')\n+        value = stream.readline().strip()\n+        stream.close()\n+        if len(value) > 0:\n+            return value\n+        elif (self.__defaults.has_key(name)):\n+            return self.__defaults[name]\n+        else:\n+            return None\n+\n+    def getall(self, name):\n+        stream = os.popen('git repo-config --get-all %s' % name, 'r')\n+        values = [line.strip() for line in stream]\n+        stream.close()\n+        return values\n+\n+    def getint(self, name):\n+        value = self.get(name)\n+        if value.isdigit():\n+            return int(value)\n+        else:\n+            raise GitConfigException, 'Value for \"%s\" is not an integer: \"%s\"' % (name, value)\n+\n+    def set(self, name, value):\n+        self.__run('git-repo-config', [name, value])\n+\n+    def sections_matching(self, regexp):\n+        \"\"\"Takes a regexp with a single group, matches it against all\n+        config variables, and returns a list whose members are the\n+        group contents, for all variable names matching the regexp.\n+        \"\"\"\n+        result = []\n+        stream = os.popen('git repo-config --get-regexp \"^%s$\"' % regexp, 'r')\n+        for line in stream:\n+            m = re.match('^%s ' % regexp, line)\n+            if m:\n+                result.append(m.group(1))\n+        stream.close()\n+        return result\n+        \n+config=GitConfig()\n \n def config_setup():\n     global config\n \n-    # Set the defaults\n-    config.add_section('stgit')\n-    config.set('stgit', 'autoresolved', 'no')\n-    config.set('stgit', 'smtpserver', 'localhost:25')\n-    config.set('stgit', 'smtpdelay', '5')\n-    config.set('stgit', 'pullcmd', 'git-pull')\n-    config.set('stgit', 'merger',\n-               'diff3 -L current -L ancestor -L patched -m -E ' \\\n-               '\"%(branch1)s\" \"%(ancestor)s\" \"%(branch2)s\" > \"%(output)s\"')\n-    config.set('stgit', 'autoimerge', 'no')\n-    config.set('stgit', 'keeporig', 'yes')\n-    config.set('stgit', 'keepoptimized', 'no')\n-    config.set('stgit', 'extensions', '.ancestor .current .patched')\n-    config.set('stgit', 'shortnr', '5')\n-\n-    # Read the configuration files (if any) and override the default settings\n-    # stgitrc are read for backward compatibility\n-    config.read('/etc/stgitrc')\n-    config.read(os.path.expanduser('~/.stgitrc'))\n-    config.read(os.path.join(basedir.get(), 'stgitrc'))\n-\n-    # GIT configuration files can have a [stgit] section\n-    try:\n-        global_config = os.environ['GIT_CONFIG']\n-    except KeyError:\n-        global_config = os.path.expanduser('~/.gitconfig')\n-    try:\n-        local_config = os.environ['GIT_CONFIG_LOCAL']\n-    except KeyError:\n-        local_config = os.path.join(basedir.get(), 'config')\n-    config.readfp(git_config(global_config))\n-    config.readfp(git_config(local_config))\n-\n     # Set the PAGER environment to the config value (if any)\n-    if config.has_option('stgit', 'pager'):\n-        os.environ['PAGER'] = config.get('stgit', 'pager')\n-\n-    # [gitmergeonefile] section is deprecated. In case it exists copy the\n-    # options/values to the [stgit] one\n-    if config.has_section('gitmergeonefile'):\n-        for option, value in config.items('gitmergeonefile'):\n-            config.set('stgit', option, value)\n-\n+    pager = config.get('stgit.pager')\n+    if pager:\n+        os.environ['PAGER'] = pager\n+    # FIXME: handle EDITOR the same way ?\n \n class ConfigOption:\n     \"\"\"Delayed cached reading of a configuration option.\n@@ -114,7 +120,7 @@ class ConfigOption:\n \n     def __str__(self):\n         if not self.__value:\n-            self.__value = config.get(self.__section, self.__option)\n+            self.__value = config.get(self.__section + '.' + self.__option)\n         return self.__value\n \n \n@@ -127,7 +133,7 @@ def file_extensions():\n     global __extensions\n \n     if not __extensions:\n-        cfg_ext = config.get('stgit', 'extensions').split()\n+        cfg_ext = config.get('stgit.extensions').split()\n         if len(cfg_ext) != 3:\n             raise CmdException, '\"extensions\" configuration error'\n \ndiff --git a/stgit/git.py b/stgit/git.py\nindex 7d99338..249080e 100644\n--- a/stgit/git.py\n+++ b/stgit/git.py\n@@ -449,10 +449,10 @@ def user():\n     \"\"\"\n     global __user\n     if not __user:\n-        if config.has_option('user', 'name') \\\n-               and config.has_option('user', 'email'):\n-            __user = Person(config.get('user', 'name'),\n-                            config.get('user', 'email'))\n+        name=config.get('user.name')\n+        email=config.get('user.email')\n+        if name and email:\n+            __user = Person(name, email)\n         else:\n             raise GitException, 'unknown user details'\n     return __user;\n@@ -818,7 +818,7 @@ def pull(repository = 'origin', refspec = None):\n     if refspec:\n         args.append(refspec)\n \n-    if __run(config.get('stgit', 'pullcmd'), args) != 0:\n+    if __run(config.get('stgit.pullcmd'), args) != 0:\n         raise GitException, 'Failed \"git-pull %s\"' % repository\n \n def repack():\ndiff --git a/stgit/gitmergeonefile.py b/stgit/gitmergeonefile.py\nindex 587c8c4..b9d02aa 100644\n--- a/stgit/gitmergeonefile.py\n+++ b/stgit/gitmergeonefile.py\n@@ -102,9 +102,8 @@ def interactive_merge(filename):\n     \"\"\"Run the interactive merger on the given file. Note that the\n     index should not have any conflicts.\n     \"\"\"\n-    try:\n-        imerger = config.get('stgit', 'imerger')\n-    except Exception, err:\n+    imerger = config.get('stgit.imerger')\n+    if not imerger:\n         raise GitMergeException, 'Configuration error: %s' % err\n \n     extensions = file_extensions()\n@@ -185,7 +184,7 @@ def merge(orig_hash, file1_hash, file2_hash,\n                     os.system('git-update-index --cacheinfo %s %s %s'\n                               % (file1_mode, file1_hash, path))\n \n-                    if config.get('stgit', 'autoimerge') == 'yes':\n+                    if config.get('stgit.autoimerge') == 'yes':\n                         print >> sys.stderr, \\\n                               'Trying the interactive merge'\n                         try:\ndiff --git a/stgit/stack.py b/stgit/stack.py\nindex 5237084..6281f36 100644\n--- a/stgit/stack.py\n+++ b/stgit/stack.py\n@@ -92,8 +92,9 @@ def edit_file(series, line, comment, show_patch = True):\n     f.close()\n \n     # the editor\n-    if config.has_option('stgit', 'editor'):\n-        editor = config.get('stgit', 'editor')\n+    editor = config.get('stgit.editor')\n+    if editor:\n+        pass\n     elif 'EDITOR' in os.environ:\n         editor = os.environ['EDITOR']\n     else:\n"},{"id":"32986","messageId":"20070129230530.7102.65330.stgit@gandelf.nowhere.earth","threadId":"6575","inReplyTo":"20070129230117.7102.64322.stgit@gandelf.nowhere.earth","subject":"[PATCH 2/7] Add caching to the new config class.","fromName":"Yann Dirson","fromEmail":"ydirson@altern.org","sentAt":"2007-01-29T23:05:30Z","receivedAt":"2007-01-29T23:05:30Z","isPatch":true,"sender":{"key":"ydirson@altern.org","avatar":"https://avatars.githubusercontent.com/u/1190950?v=4"},"body":"\n\n\nSigned-off-by: Yann Dirson <ydirson@altern.org>\n---\n\n stgit/config.py |   19 ++++++++++++++++---\n 1 files changed, 16 insertions(+), 3 deletions(-)\n\ndiff --git a/stgit/config.py b/stgit/config.py\nindex d42754b..901f600 100644\n--- a/stgit/config.py\n+++ b/stgit/config.py\n@@ -39,6 +39,8 @@ class GitConfig:\n         'stgit.shortnr':\t '5'\n         }\n \n+    __cache={}\n+\n     def __run(self, cmd, args=None):\n         \"\"\"__run: runs cmd using spawnvp.\n     \n@@ -59,20 +61,31 @@ class GitConfig:\n         return 0\n     \n     def get(self, name):\n+        if self.__cache.has_key(name):\n+            return self.__cache[name]\n+\n         stream = os.popen('git repo-config --get %s' % name, 'r')\n         value = stream.readline().strip()\n         stream.close()\n         if len(value) > 0:\n-            return value\n+            pass\n         elif (self.__defaults.has_key(name)):\n-            return self.__defaults[name]\n+            value = self.__defaults[name]\n         else:\n-            return None\n+            value = None\n+\n+        self.__cache[name] = value\n+        return value\n \n     def getall(self, name):\n+        if self.__cache.has_key(name):\n+            return self.__cache[name]\n+\n         stream = os.popen('git repo-config --get-all %s' % name, 'r')\n         values = [line.strip() for line in stream]\n         stream.close()\n+\n+        self.__cache[name] = values\n         return values\n \n     def getint(self, name):\n"},{"id":"32988","messageId":"20070129230535.7102.54749.stgit@gandelf.nowhere.earth","threadId":"6575","inReplyTo":"20070129230117.7102.64322.stgit@gandelf.nowhere.earth","subject":"[PATCH 3/7] Move identification of parent branch's remote def up into stack class.","fromName":"Yann Dirson","fromEmail":"ydirson@altern.org","sentAt":"2007-01-29T23:05:35Z","receivedAt":"2007-01-29T23:05:35Z","isPatch":true,"sender":{"key":"ydirson@altern.org","avatar":"https://avatars.githubusercontent.com/u/1190950?v=4"},"body":"\nAlso adds the __set_parent_remote() counterpart method, but only\nprivate since it is expected to be called only through set_parent, to\nbe introduced in subsequent patch.\n\nSigned-off-by: Yann Dirson <ydirson@altern.org>\n---\n\n stgit/commands/pull.py |    2 +-\n stgit/stack.py         |    6 ++++++\n 2 files changed, 7 insertions(+), 1 deletions(-)\n\ndiff --git a/stgit/commands/pull.py b/stgit/commands/pull.py\nindex e47858a..0608fb6 100644\n--- a/stgit/commands/pull.py\n+++ b/stgit/commands/pull.py\n@@ -53,7 +53,7 @@ def func(parser, options, args):\n     if len(args) >= 1:\n         repository = args[0]\n     else:\n-        repository = config.get('branch.%s.remote' % git.get_head_file()) or 'origin'\n+        repository = crt_series.get_parent_remote()\n \n     refspec = None\n     if len(args) == 2:\ndiff --git a/stgit/stack.py b/stgit/stack.py\nindex 6281f36..947b416 100644\n--- a/stgit/stack.py\n+++ b/stgit/stack.py\n@@ -407,6 +407,12 @@ class Series(StgitObject):\n     def set_description(self, line):\n         self._set_field('description', line)\n \n+    def get_parent_remote(self):\n+        return config.get('branch.%s.remote' % self.__name) or 'origin'\n+\n+    def __set_parent_remote(self, remote):\n+        value = config.set('branch.%s.remote' % self.__name, remote)\n+\n     def __patch_is_current(self, patch):\n         return patch.get_name() == self.get_current()\n \n"},{"id":"32985","messageId":"20070129230540.7102.43825.stgit@gandelf.nowhere.earth","threadId":"6575","inReplyTo":"20070129230117.7102.64322.stgit@gandelf.nowhere.earth","subject":"[PATCH 4/7] Allows extraction of information about remotes.","fromName":"Yann Dirson","fromEmail":"ydirson@altern.org","sentAt":"2007-01-29T23:05:40Z","receivedAt":"2007-01-29T23:05:40Z","isPatch":true,"sender":{"key":"ydirson@altern.org","avatar":"https://avatars.githubusercontent.com/u/1190950?v=4"},"body":"\nWe want to know the list of declared remotes, the local branches they\nhold, and which remotes holds a given branch.  All this regardless of\nwhere the information is stored.\n\nIf there are any git-1.5 remotes declared in .git/config, we suppose\nyou know what you're doing and they will take precedence on any\npre-1.5 remotes.\n\nThis does not use git-remote for now, since it is 1.5 material not\nreleased yet, does not support legacy branches/ remotes, and does not\nallow yet to query all of the information we need.\n\nSigned-off-by: Yann Dirson <ydirson@altern.org>\n---\n\n stgit/git.py |   68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 68 insertions(+), 0 deletions(-)\n\ndiff --git a/stgit/git.py b/stgit/git.py\nindex 249080e..038aaac 100644\n--- a/stgit/git.py\n+++ b/stgit/git.py\n@@ -881,3 +881,71 @@ def modifying_revs(files, base_rev):\n     revs = [line.strip() for line in _output_lines(cmd + files)]\n \n     return revs\n+\n+\n+def refspec_localpart(refspec):\n+    m = re.match('^[^:]*:([^:]*)$', refspec)\n+    if m:\n+        return m.group(1)\n+    else:\n+        raise GitException, 'Cannot parse refspec \"%s\"' % line\n+\n+def refspec_remotepart(refspec):\n+    m = re.match('^([^:]*):[^:]*$', refspec)\n+    if m:\n+        return m.group(1)\n+    else:\n+        raise GitException, 'Cannot parse refspec \"%s\"' % line\n+    \n+\n+def __remotes_from_config():\n+    return config.sections_matching(r'remote\\.(.*)\\.url')\n+\n+def __remotes_from_dir(dir):\n+    return os.listdir(os.path.join(basedir.get(), dir))\n+\n+def remotes_list():\n+    \"\"\"Return the list of remotes in the repository\n+    \"\"\"\n+\n+    return set(__remotes_from_config()) | \\\n+           set(__remotes_from_dir('remotes')) | \\\n+           set(__remotes_from_dir('branches'))\n+\n+def remotes_local_branches(remote):\n+    \"\"\"Returns the list of local branches fetched from given remote\n+    \"\"\"\n+\n+    branches = []\n+    if remote in __remotes_from_config():\n+        for line in config.getall('remote.%s.fetch' % remote):\n+            branches.append(refspec_localpart(line))\n+    elif remote in __remotes_from_dir('remotes'):\n+        stream = open(os.path.join(basedir.get(), 'remotes', remote), 'r')\n+        for line in stream:\n+            # Only consider Pull lines\n+            m = re.match('^Pull: (.*)\\n$', line)\n+            branches.append(refspec_localpart(m.group(1)))\n+        stream.close()\n+    elif remote in __remotes_from_dir('branches'):\n+        # old-style branches only declare one branch\n+        branches.append('refs/heads/'+remote);\n+    else:\n+        raise GitException, 'Unknown remote \"%s\"' % remote\n+\n+    return branches\n+\n+def identify_remote(branchname):\n+    \"\"\"Return the name for the remote to pull the given branchname\n+    from, or None if we believe it is a local branch.\n+    \"\"\"\n+\n+    for remote in remotes_list():\n+        if branchname in remotes_local_branches(remote):\n+            return remote\n+\n+    # FIXME: in the case of local branch we should maybe set remote to\n+    # \".\" but are we even sure it is the only case left ?\n+\n+    # if we get here we've found nothing\n+    return None\n"},{"id":"32990","messageId":"20070129230545.7102.92460.stgit@gandelf.nowhere.earth","threadId":"6575","inReplyTo":"20070129230117.7102.64322.stgit@gandelf.nowhere.earth","subject":"[PATCH 5/7] Basic support for keeping a ref to the parent branch.","fromName":"Yann Dirson","fromEmail":"ydirson@altern.org","sentAt":"2007-01-29T23:05:45Z","receivedAt":"2007-01-29T23:05:45Z","isPatch":true,"sender":{"key":"ydirson@altern.org","avatar":"https://avatars.githubusercontent.com/u/1190950?v=4"},"body":"\nThis adds a framework to handle the parent branch of a stack, in\naddition to the parent remote, and to set them when creating a stack.\n\nSigned-off-by: Yann Dirson <ydirson@altern.org>\n---\n\n TODO           |    2 --\n stgit/stack.py |   24 +++++++++++++++++++++++-\n 2 files changed, 23 insertions(+), 3 deletions(-)\n\ndiff --git a/TODO b/TODO\nindex 549bc9d..884b831 100644\n--- a/TODO\n+++ b/TODO\n@@ -20,8 +20,6 @@ The future, when time allows or if someone else does them:\n - multiple heads in a patch - useful for forking a patch,\n   synchronising with other patches (diff format or in other\n   repositories)\n-- \"pull\" argument should default to a sane value, \"origin\" is wrong in\n-  many cases\n - commit directly to a patch which is not top\n - patch synchronisation between between branches (as some people,\n   including me have the same patches based on different branches and\ndiff --git a/stgit/stack.py b/stgit/stack.py\nindex 947b416..9640eb5 100644\n--- a/stgit/stack.py\n+++ b/stgit/stack.py\n@@ -413,6 +413,26 @@ class Series(StgitObject):\n     def __set_parent_remote(self, remote):\n         value = config.set('branch.%s.remote' % self.__name, remote)\n \n+    def get_parent_branch(self):\n+        value = config.get('branch.%s.merge' % self.__name)\n+        if value:\n+            return value\n+        elif rev_parse('heads/origin'):\n+            return 'heads/origin'\n+        else:\n+            raise StackException, 'Cannot find a parent branch for \"%s\"' % self.__name\n+\n+    def __set_parent_branch(self, name):\n+        config.set('branch.%s.merge' % self.__name, name)\n+\n+    def set_parent(self, remote, localbranch):\n+        if localbranch:\n+            self.__set_parent_branch(localbranch)\n+            if remote:\n+                self.__set_parent_remote(remote)\n+        elif remote:\n+            raise StackException, 'Remote \"%s\" without a branch cannot be used as parent' % remote\n+\n     def __patch_is_current(self, patch):\n         return patch.get_name() == self.get_current()\n \n@@ -466,7 +486,7 @@ class Series(StgitObject):\n         \"\"\"\n         return os.path.isdir(self.__patch_dir)\n \n-    def init(self, create_at=False):\n+    def init(self, create_at=False, parent_remote=None, parent_branch=None):\n         \"\"\"Initialises the stgit series\n         \"\"\"\n         bases_dir = os.path.join(self.__base_dir, 'refs', 'bases')\n@@ -483,6 +503,8 @@ class Series(StgitObject):\n \n         os.makedirs(self.__patch_dir)\n \n+        self.set_parent(parent_remote, parent_branch)\n+        \n         create_dirs(bases_dir)\n \n         self.create_empty_field('applied')\n"},{"id":"32994","messageId":"20070129230550.7102.68705.stgit@gandelf.nowhere.earth","threadId":"6575","inReplyTo":"20070129230117.7102.64322.stgit@gandelf.nowhere.earth","subject":"[PATCH 6/7] Have 'stg branch --create' record parent information.","fromName":"Yann Dirson","fromEmail":"ydirson@altern.org","sentAt":"2007-01-29T23:05:50Z","receivedAt":"2007-01-29T23:05:50Z","isPatch":true,"sender":{"key":"ydirson@altern.org","avatar":"https://avatars.githubusercontent.com/u/1190950?v=4"},"body":"\n\n\nSigned-off-by: Yann Dirson <ydirson@altern.org>\n---\n\n stgit/commands/branch.py |   37 ++++++++++++++++++++++++++++++++++---\n 1 files changed, 34 insertions(+), 3 deletions(-)\n\ndiff --git a/stgit/commands/branch.py b/stgit/commands/branch.py\nindex d348409..f074d47 100644\n--- a/stgit/commands/branch.py\n+++ b/stgit/commands/branch.py\n@@ -123,10 +123,41 @@ def func(parser, options, args):\n         check_head_top_equal()\n \n         tree_id = None\n-        if len(args) == 2:\n+        if len(args) >= 2:\n+            try:\n+                if git.rev_parse(args[1]) == git.rev_parse('refs/heads/' + args[1]):\n+                    # we are for sure refering to a branch\n+                    parentbranch = 'refs/heads/' + args[1]\n+                    print 'Recording \"%s\" as parent branch.' % parentbranch\n+                elif git.rev_parse(args[1]) and re.search('/', args[1]):\n+                    # FIXME: should the test be more strict ?\n+                    parentbranch = args[1]\n+                else:\n+                    # Note: this includes refs to StGIT patches\n+                    print 'Don\\'t know how to determine parent branch from \"%s\".' % args[1]\n+                    parentbranch = None\n+            except git.GitException:\n+                # should use a more specific exception to catch only non-git refs ?\n+                print 'Don\\'t know how to determine parent branch from \"%s\".' % args[1]\n+                parentbranch = None\n+\n             tree_id = git_id(args[1])\n-        \n-        stack.Series(args[0]).init(create_at = tree_id)\n+        else:\n+            # branch stack off current branch\n+            parentbranch = git.get_head_file()\n+\n+        if parentbranch:\n+            parentremote = git.identify_remote(parentbranch)\n+            if parentremote:\n+                print 'Using \"%s\" remote to pull parent from.' % parentremote\n+            else:\n+                print 'Not identified a remote to pull parent from.'\n+        else:\n+            parentremote = None\n+\n+        stack.Series(args[0]).init(create_at = tree_id,\n+                                   parent_remote = parentremote,\n+                                   parent_branch = parentbranch)\n \n         print 'Branch \"%s\" created.' % args[0]\n         return\n"},{"id":"32995","messageId":"20070129230555.7102.93948.stgit@gandelf.nowhere.earth","threadId":"6575","inReplyTo":"20070129230117.7102.64322.stgit@gandelf.nowhere.earth","subject":"[PATCH 7/7] Make 'stg pull' use git-fetch and not git-pull.","fromName":"Yann Dirson","fromEmail":"ydirson@altern.org","sentAt":"2007-01-29T23:05:56Z","receivedAt":"2007-01-29T23:05:56Z","isPatch":true,"sender":{"key":"ydirson@altern.org","avatar":"https://avatars.githubusercontent.com/u/1190950?v=4"},"body":"\nWe introduce a new pull-does-rebase setting, as companion to pullcmd.\nThis allows to use both pullcmd's that need a rebase (like git-fetch)\nand pullcmd's that do not (like git-pull).\n\nTo be able to rebase, we rely on parent information being available\nfor the stack.\n\nSigned-off-by: Yann Dirson <ydirson@altern.org>\n---\n\n examples/gitconfig |    8 +++++++-\n stgit/config.py    |    3 ++-\n stgit/git.py       |   15 ++++++++++-----\n 3 files changed, 19 insertions(+), 7 deletions(-)\n\ndiff --git a/examples/gitconfig b/examples/gitconfig\nindex ee68d07..60f6e85 100644\n--- a/examples/gitconfig\n+++ b/examples/gitconfig\n@@ -33,8 +33,14 @@\n \t#pager = ~/share/stgit/contrib/diffcol.sh\n \t#pager = filterdiff --annotate | colordiff | less -FRX\n \n-\t# GIT pull command (should take the same arguments as git-pull)\n+\t# GIT pull command (should take the same arguments as\n+\t# git-fetch or git-pull).  By default:\n+\t#pullcmd = git-fetch\n+\t#pull-does-rebase = yes\n+\t# Alternative (old behaviour), less intuitive but maybe useful\n+\t# for some workflows:\n \t#pullcmd = git-pull\n+\t#pull-does-rebase = no\n \n \t# The three-way merge tool. Note that the 'output' file contains the\n \t# same data as 'branch1'. This is useful for tools that do not take an\ndiff --git a/stgit/config.py b/stgit/config.py\nindex 901f600..48b4e2d 100644\n--- a/stgit/config.py\n+++ b/stgit/config.py\n@@ -29,7 +29,8 @@ class GitConfig:\n         'stgit.autoresolved':\t'no',\n         'stgit.smtpserver':\t'localhost:25',\n         'stgit.smtpdelay':\t'5',\n-        'stgit.pullcmd':\t'git-pull',\n+        'stgit.pullcmd':\t'git-fetch',\n+        'stgit.pull-does-rebase': 'yes',\n         'stgit.merger':\t\t'diff3 -L current -L ancestor -L patched -m -E ' \\\n \t\t\t\t'\"%(branch1)s\" \"%(ancestor)s\" \"%(branch2)s\" > \"%(output)s\"',\n         'stgit.autoimerge':\t'no',\ndiff --git a/stgit/git.py b/stgit/git.py\nindex 038aaac..e30b959 100644\n--- a/stgit/git.py\n+++ b/stgit/git.py\n@@ -808,18 +808,23 @@ def reset(files = None, tree_id = None, check_out = True):\n         __set_head(tree_id)\n \n def pull(repository = 'origin', refspec = None):\n-    \"\"\"Pull changes from the remote repository. At the moment, just\n-    use the 'git-pull' command\n+    \"\"\"Pull changes from the remote repository. Uses 'git-fetch'\n+    and moves the stack base.\n     \"\"\"\n-    # 'git-pull' updates the HEAD\n+    # we update the HEAD\n     __clear_head_cache()\n \n     args = [repository]\n     if refspec:\n         args.append(refspec)\n \n-    if __run(config.get('stgit.pullcmd'), args) != 0:\n-        raise GitException, 'Failed \"git-pull %s\"' % repository\n+    command = config.get('stgit.pullcmd')\n+    if __run(command, args) != 0:\n+        raise GitException, 'Failed \"%s %s\"' % (command, repository)\n+\n+    if (config.get('stgit.pull-does-rebase')):\n+        # FIXME!\n+        reset(tree_id = rev_parse(repository))\n \n def repack():\n     \"\"\"Repack all objects into a single pack\n"},{"id":"33190","messageId":"b0943d9e0701311544k22986545x351c04a8faab8814@mail.gmail.com","threadId":"6575","inReplyTo":"20070129230555.7102.93948.stgit@gandelf.nowhere.earth","subject":"Re: [PATCH 7/7] Make 'stg pull' use git-fetch and not git-pull.","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2007-01-31T23:44:07Z","receivedAt":"2007-01-31T23:44:07Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"On 29/01/07, Yann Dirson <ydirson@altern.org> wrote:\n> We introduce a new pull-does-rebase setting, as companion to pullcmd.\n> This allows to use both pullcmd's that need a rebase (like git-fetch)\n> and pullcmd's that do not (like git-pull).\n\nThanks for the patches. I applied all of them but I have a question on this one:\n\n> +    if (config.get('stgit.pull-does-rebase')):\n> +        # FIXME!\n> +        reset(tree_id = rev_parse(repository))\n\nWhy the \"FIXME\" here? The alternative is switch() and it might be safer.\n\n-- \nCatalin\n"},{"id":"33268","messageId":"20070201184705.GC5362@nan92-1-81-57-214-146.fbx.proxad.net","threadId":"6575","inReplyTo":"b0943d9e0701311544k22986545x351c04a8faab8814@mail.gmail.com","subject":"Re: [PATCH 7/7] Make 'stg pull' use git-fetch and not git-pull.","fromName":"Yann Dirson","fromEmail":"ydirson@altern.org","sentAt":"2007-02-01T18:47:05Z","receivedAt":"2007-02-01T18:47:05Z","isPatch":true,"sender":{"key":"ydirson@altern.org","avatar":"https://avatars.githubusercontent.com/u/1190950?v=4"},"body":"On Wed, Jan 31, 2007 at 11:44:07PM +0000, Catalin Marinas wrote:\n> On 29/01/07, Yann Dirson <ydirson@altern.org> wrote:\n> >We introduce a new pull-does-rebase setting, as companion to pullcmd.\n> >This allows to use both pullcmd's that need a rebase (like git-fetch)\n> >and pullcmd's that do not (like git-pull).\n> \n> Thanks for the patches. I applied all of them but I have a question on this \n> one:\n> \n> >+    if (config.get('stgit.pull-does-rebase')):\n> >+        # FIXME!\n> >+        reset(tree_id = rev_parse(repository))\n> \n> Why the \"FIXME\" here? The alternative is switch() and it might be safer.\n\nThe last 2 patches, including the one with this FIXME, will need some\nwork.\n\nIn this case, we should reset to the parent branch.  It currently just\nworks because traditionnaly we use refs/heads/origin from remote\n\"origin\", and \"rev-parse origin\" resolves to that branch.\n\n\nBest regards,\n-- \nYann\n"}]}