changeset 304:6c56a595dc1c cannam-pre-20110113-merge

Merge from branch "feature_73"
author Chris Cannam
date Mon, 28 Mar 2011 17:32:56 +0100
parents 93b4cfd3aaaa (current diff) defe55be97b9 (diff)
children f5a3d57f3d90
files config/locales/en-GB.yml config/locales/en.yml config/routes.rb
diffstat 8 files changed, 338 insertions(+), 33 deletions(-) [+]
line wrap: on
line diff
--- a/app/controllers/sys_controller.rb	Mon Mar 28 17:30:04 2011 +0100
+++ b/app/controllers/sys_controller.rb	Mon Mar 28 17:32:56 2011 +0100
@@ -55,6 +55,20 @@
     render :nothing => true, :status => 404
   end
 
+  def get_external_repo_url
+    project = Project.find(params[:id])
+    if project.repository
+      repo = project.repository
+      if repo.is_external?
+        render :text => repo.external_url, :status => 200
+      else
+        render :nothing => true, :status => 200
+      end
+    end
+  rescue ActiveRecord::RecordNotFound
+    render :nothing => true, :status => 404
+  end
+
   def set_embedded_active
     project = Project.find(params[:id])
     mods = project.enabled_modules
--- a/app/helpers/repositories_helper.rb	Mon Mar 28 17:30:04 2011 +0100
+++ b/app/helpers/repositories_helper.rb	Mon Mar 28 17:32:56 2011 +0100
@@ -145,12 +145,6 @@
     send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method) && method != 'repository_field_tags'
   end
   
-  
-  def ssamr_scm_update(repository)
-    check_box_tag('repository_scm', value = "1", checked = false, onchange => remote_function(:url => { :controller => 'repositories', :action => 'ssamr_edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)"))
-  
-  end
-  
   def scm_select_tag(repository)
     scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
     Redmine::Scm::Base.all.each do |scm|
--- a/app/views/projects/settings/_repository.rhtml	Mon Mar 28 17:30:04 2011 +0100
+++ b/app/views/projects/settings/_repository.rhtml	Mon Mar 28 17:32:56 2011 +0100
@@ -10,17 +10,16 @@
 
 <div class="box tabular">
 
+<p>
+<% if @repository %>
+   <%= l(:text_settings_repo_explanation) %></ br>
+   <% if @repository.is_external %>
+     <p><%= l(:text_settings_repo_is_external) %></ br>
+   <% else %>
+     <p><%= l(:text_settings_repo_is_internal) %></ br>
+   <% end %>
+</p>
 
-<% if @repository %>
-   <% if @repository.is_external %>
-   <%= l(:text_settings_repo_is_external) %></ br>
-   <% else %>
-   <%= l(:text_settings_repo_is_internal) %></ br>
-<% end %>
-   <% else %>
-	<%= l(:text_settings_repo_creation) %></ br>
-<% end %>
-  
 
 
 
@@ -28,16 +27,17 @@
 <p>
 	<%= label_tag('repository_is_external', l(:label_is_external_repository)) %>
 	<%= check_box :repository, :is_external, :onclick => "toggle_ext_url()" %> 
-	<%= l(:setting_external_repository) %>
+	<br/><em><%= l(:setting_external_repository) %></em>
 </p>
 
 
 <p>
 	<%= label_tag('repository_external_url', l(:label_repository_external_url)) %>
-	<%= text_field :repository, :external_url, :disabled => true %> 
-	<%= l(:setting_external_repository_url) %>
+	<%= text_field :repository, :external_url, :disabled => !(@repository and @repository.is_external) %> 
+	<br/><em><%= l(:setting_external_repository_url) %></em>
 </p>
 
+<p><%= l(:text_settings_repo_need_help) %></p>
 
 </div>
 
@@ -48,4 +48,9 @@
 </div>
 
 <%= submit_tag(l(:button_save), :onclick => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)")) %>
+
+<% else %>
+   <%= l(:text_settings_repo_creation) %></ br>
 <% end %>
+
+<% end %>
--- a/config/locales/en.yml	Mon Mar 28 17:30:04 2011 +0100
+++ b/config/locales/en.yml	Mon Mar 28 17:32:56 2011 +0100
@@ -306,9 +306,9 @@
   field_text: Text field
   field_visible: Visible
  
-  setting_external_repository: "In the case you wish to follow an external repository"
-  setting_external_repository_url: "The external repository URL"
-  label_repository_external_url: "External rep URL"
+  setting_external_repository: "Select this if the project's main repository is hosted somewhere else"
+  setting_external_repository_url: "The URL of the existing external repository. Must be publicly accessible without a password"
+  label_repository_external_url: "External repository URL"
   setting_tipoftheday_text: Tip of the Day
   setting_notifications_text: Notifications
   field_terms_and_conditions: 'Terms and Conditions:'
@@ -634,7 +634,7 @@
   label_not_contains: doesn't contain
   label_day_plural: days
   label_repository: Repository
-  label_is_external_repository: External?
+  label_is_external_repository: Track an external repository
   label_repository_plural: Repositories
   label_browse: Browse
   label_modification: "{{count}} change"
@@ -918,7 +918,7 @@
   text_enumeration_destroy_question: "{{count}} objects are assigned to this value."
   text_enumeration_category_reassign_to: 'Reassign them to this value:'
   text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
-  text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
+  text_repository_usernames_mapping: "Select the project member associated with each username found in the repository log.\nUsers whose name or email matches that in the repository are mapped automatically."
   text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
   text_custom_field_possible_values_info: 'One line for each value'
   text_wiki_page_destroy_question: "This page has {{descendants}} child page(s) and descendant(s). What do you want to do?"
@@ -929,9 +929,11 @@
   text_zoom_in: Zoom in
   text_zoom_out: Zoom out
   text_files_active_change: <br>Click the star to switch active status for a download on or off.<br>Active files will be shown more prominently in the download page.
-  text_settings_repo_creation: The repository for a project should be set up automatically within a few minutes of the project being created.<br>You should not have to adjust any settings here.<br>Please check again in ten minutes, and <a href="/projects/soundsoftware-site/wiki/Help">contact us</a> if there is any problem.
-  text_settings_repo_is_internal: The repository for this project is an internal Mercurial Repository, hosted by SoundSoftware.ac.uk. 
-  text_settings_repo_is_external: You are tracking an external repository, with a mirror Mercurial repository hosted by SoundSoftware.ac.uk.
+  text_settings_repo_creation: <b>Creating repository...</b></p><p>The source code repository for a project will be set up automatically within a few minutes of the project being created.</p><p>Please check again in five minutes, and <a href="/projects/soundsoftware-site/wiki/Help">contact us</a> if there is any problem.</p><p>If you wish to use this project to track a repository that is already hosted somewhere else, please wait until the repository has been created here and then return to this settings page to configure it.</p><p>If you don't want a repository at all, go to the Modules tab and switch it off there.
+  text_settings_repo_explanation: <b>External repositories</b><p>Normally your project's primary repository will be the Mercurial repository hosted at this site.<p>However, if you already have your project hosted somewhere else, you can specify your existing external repository's URL here &ndash; then this site will track that repository in a read-only &ldquo;mirror&rdquo; copy.  External Mercurial, git and Subversion repositories can be tracked. Note that you cannot switch to an external repository if you have already made any commits to the repository hosted here.
+  text_settings_repo_is_internal: Currently the repository hosted at this site is the primary repository for this project.
+  text_settings_repo_is_external: Currently the repository hosted at this site is a read-only copy of an external repository.
+  text_settings_repo_need_help: Please <a href="/projects/soundsoftware-site/wiki/Help">contact us</a> if you need help deciding how best to set this up.<br>We can also import complete revision history from other systems into a new primary repository for you if you wish.
 
 
  
--- a/config/routes.rb	Mon Mar 28 17:30:04 2011 +0100
+++ b/config/routes.rb	Mon Mar 28 17:32:56 2011 +0100
@@ -236,6 +236,7 @@
   map.with_options :controller => 'sys' do |sys|
     sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
     sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
+    sys.connect 'sys/projects/:id/external-repository.:format', :action => 'get_external_repo_url', :conditions => {:method => :get}
     sys.connect 'sys/projects/:id/embedded.:format', :action => 'set_embedded_active', :conditions => { :method => :post }
   end
  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extra/soundsoftware/convert-external-repos.rb	Mon Mar 28 17:32:56 2011 +0100
@@ -0,0 +1,173 @@
+#!/usr/bin/env ruby
+
+# == Synopsis
+#
+# convert-external-repos: Update local Mercurial mirrors of external repos,
+# by running an external command for each project requiring an update.
+#
+# == Usage
+#
+#    convert-external-repos [OPTIONS...] -s [DIR] -r [HOST]
+#     
+# == Arguments (mandatory)
+#
+#   -s, --scm-dir=DIR         use DIR as base directory for repositories
+#   -r, --redmine-host=HOST   assume Redmine is hosted on HOST. Examples:
+#                             -r redmine.example.net
+#                             -r http://redmine.example.net
+#                             -r https://example.net/redmine
+#   -k, --key=KEY             use KEY as the Redmine API key
+#   -c, --command=COMMAND     use this command to update each external
+#                             repository: command is called with the name
+#                             of the project, the path to its repo, and
+#                             its external repo url as its three args
+#
+# == Options
+#
+#   --http-user=USER          User for HTTP Basic authentication with Redmine WS
+#   --http-pass=PASSWORD      Password for Basic authentication with Redmine WS
+#   -t, --test                only show what should be done
+#   -h, --help                show help and exit
+#   -v, --verbose             verbose
+#   -V, --version             print version and exit
+#   -q, --quiet               no log
+
+
+require 'getoptlong'
+require 'rdoc/usage'
+require 'find'
+require 'etc'
+
+Version = "1.0"
+
+opts = GetoptLong.new(
+                      ['--scm-dir',      '-s', GetoptLong::REQUIRED_ARGUMENT],
+                      ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
+                      ['--key',          '-k', GetoptLong::REQUIRED_ARGUMENT],
+                      ['--http-user',          GetoptLong::REQUIRED_ARGUMENT],
+                      ['--http-pass',          GetoptLong::REQUIRED_ARGUMENT],
+                      ['--command' ,     '-c', GetoptLong::REQUIRED_ARGUMENT],
+                      ['--test',         '-t', GetoptLong::NO_ARGUMENT],
+                      ['--verbose',      '-v', GetoptLong::NO_ARGUMENT],
+                      ['--version',      '-V', GetoptLong::NO_ARGUMENT],
+                      ['--help'   ,      '-h', GetoptLong::NO_ARGUMENT],
+                      ['--quiet'  ,      '-q', GetoptLong::NO_ARGUMENT]
+                      )
+
+$verbose      = 0
+$quiet        = false
+$redmine_host = ''
+$repos_base   = ''
+$http_user    = ''
+$http_pass    = ''
+$test         = false
+
+def log(text, options={})
+  level = options[:level] || 0
+  puts text unless $quiet or level > $verbose
+  exit 1 if options[:exit]
+end
+
+def system_or_raise(command)
+  raise "\"#{command}\" failed" unless system command
+end
+
+begin
+  opts.each do |opt, arg|
+    case opt
+    when '--scm-dir';        $repos_base   = arg.dup
+    when '--redmine-host';   $redmine_host = arg.dup
+    when '--key';            $api_key      = arg.dup
+    when '--http-user';      $http_user    = arg.dup
+    when '--http-pass';      $http_pass    = arg.dup
+    when '--command';        $command      = arg.dup
+    when '--verbose';        $verbose += 1
+    when '--test';           $test = true
+    when '--version';        puts Version; exit
+    when '--help';           RDoc::usage
+    when '--quiet';          $quiet = true
+    end
+  end
+rescue
+  exit 1
+end
+
+if $test
+  log("running in test mode")
+end
+
+if ($redmine_host.empty? or $repos_base.empty? or $command.empty?)
+  RDoc::usage
+end
+
+unless File.directory?($repos_base)
+  log("directory '#{$repos_base}' doesn't exist", :exit => true)
+end
+
+begin
+  require 'active_resource'
+rescue LoadError
+  log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
+end
+
+class Project < ActiveResource::Base
+  self.headers["User-agent"] = "SoundSoftware external repository converter/#{Version}"
+end
+
+log("querying Redmine for projects...", :level => 1);
+
+$redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
+$redmine_host.gsub!(/\/$/, '')
+
+Project.site = "#{$redmine_host}/sys";
+Project.user = $http_user;
+Project.password = $http_pass;
+
+begin
+  # Get all active projects that have the Repository module enabled
+  projects = Project.find(:all, :params => {:key => $api_key})
+rescue => e
+  log("Unable to connect to #{Project.site}: #{e}", :exit => true)
+end
+
+if projects.nil?
+  log('no project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
+end
+
+log("retrieved #{projects.size} projects", :level => 1)
+
+projects.each do |project|
+  log("treating project #{project.name}", :level => 1)
+
+  if project.identifier.empty?
+    log("\tno identifier for project #{project.name}")
+    next
+  elsif not project.identifier.match(/^[a-z0-9\-]+$/)
+    log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
+    next
+  end
+
+  if !project.respond_to?(:repository) or !project.repository.is_external?
+    log("\tproject #{project.identifier} does not use an external repository");
+    next
+  end
+
+  external_url = project.repository.external_url;
+  log("\tproject #{project.identifier} has external repository url #{external_url}");
+
+  if !external_url.match(/^[a-z][a-z+]{0,8}[a-z]:\/\//)
+    log("\tthis doesn't look like a plausible url to me, skipping")
+    next
+  end
+
+  repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
+
+  unless File.directory?(repos_path)
+    log("\tproject repo directory '#{repos_path}' doesn't exist")
+    next
+  end
+
+  system($command, project.identifier, repos_path, external_url)
+
+end
+  
--- a/extra/soundsoftware/reposman-soundsoftware.rb	Mon Mar 28 17:30:04 2011 +0100
+++ b/extra/soundsoftware/reposman-soundsoftware.rb	Mon Mar 28 17:32:56 2011 +0100
@@ -9,12 +9,12 @@
 #    reposman [OPTIONS...] -s [DIR] -r [HOST]
 #     
 #  Examples:
-#    reposman --svn-dir=/var/svn --redmine-host=redmine.example.net --scm subversion
+#    reposman --scm-dir=/var/svn --redmine-host=redmine.example.net --scm subversion
 #    reposman -s /var/git -r redmine.example.net -u http://svn.example.net --scm git
 #
 # == Arguments (mandatory)
 #
-#   -s, --svn-dir=DIR         use DIR as base directory for svn repositories
+#   -s, --scm-dir=DIR         use DIR as base directory for repositories
 #   -r, --redmine-host=HOST   assume Redmine is hosted on HOST. Examples:
 #                             -r redmine.example.net
 #                             -r http://redmine.example.net
@@ -70,7 +70,7 @@
 SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
 
 opts = GetoptLong.new(
-                      ['--svn-dir',      '-s', GetoptLong::REQUIRED_ARGUMENT],
+                      ['--scm-dir',      '-s', GetoptLong::REQUIRED_ARGUMENT],
                       ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
                       ['--key',          '-k', GetoptLong::REQUIRED_ARGUMENT],
                       ['--owner',        '-o', GetoptLong::REQUIRED_ARGUMENT],
@@ -133,7 +133,7 @@
 begin
   opts.each do |opt, arg|
     case opt
-    when '--svn-dir';        $repos_base   = arg.dup
+    when '--scm-dir';        $repos_base   = arg.dup
     when '--redmine-host';   $redmine_host = arg.dup
     when '--key';            $api_key      = arg.dup
     when '--owner';          $svn_owner    = arg.dup; $use_groupid = false;
@@ -174,7 +174,7 @@
 end
 
 unless File.directory?($repos_base)
-  log("directory '#{$repos_base}' doesn't exists", :exit => true)
+  log("directory '#{$repos_base}' doesn't exist", :exit => true)
 end
 
 begin
@@ -184,7 +184,7 @@
 end
 
 class Project < ActiveResource::Base
-  self.headers["User-agent"] = "Redmine repository manager/#{Version}"
+  self.headers["User-agent"] = "SoundSoftware repository manager/#{Version}"
 end
 
 log("querying Redmine for projects...", :level => 1);
@@ -346,5 +346,14 @@
     log("\trepository #{repos_path} created");
   end
 
+  if project.respond_to?(:repository) and project.repository.is_external?
+    external_url = project.repository.external_url;
+    log("\tproject #{project.identifier} has external repository url #{external_url}");
+    if !external_url.match(/^https?:/)
+      # wot about git, svn/svn+ssh, etc? should we just check for a scheme at all?
+      log("\tthis is not an http(s) url: ignoring");
+    end
+  end
+
 end
   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extra/soundsoftware/update-external-repo.sh	Mon Mar 28 17:32:56 2011 +0100
@@ -0,0 +1,107 @@
+#!/bin/sh
+
+mirrordir="/var/mirror"
+logfile="/var/www/test-cannam/log/update-external-repo.log"
+
+project="$1"
+local_repo="$2"
+remote_repo="$3"
+
+if [ -z "$project" ] || [ -z "$local_repo" ] || [ -z "$remote_repo" ]; then
+    echo "Usage: $0 <project> <local-repo-path> <remote-repo-url>"
+    exit 2
+fi
+
+  # We need to handle different source repository types separately.
+  # 
+  # The convert extension cannot convert directly from a remote git
+  # repo; we'd have to mirror to a local repo first.  Incremental
+  # conversions do work though.  The hg-git plugin will convert
+  # directly from remote repositories, but not via all schemes
+  # (e.g. https is not currently supported).  It's probably easier to
+  # use git itself to clone locally and then convert or hg-git from
+  # there.
+  # 
+  # We can of course convert directly from remote Subversion repos,
+  # but we need to keep track of that -- you can ask to convert into a
+  # repo that has already been used (for Mercurial) and it'll do so
+  # happily; we don't want that.
+  #
+  # Converting from a remote Hg repo should be fine!
+  #
+  # One other thing -- we can't actually tell the difference between
+  # the various SCM types based on URL alone.  We have to try them
+  # (ideally in an order determined by a guess based on the URL) and
+  # see what happens.
+
+project_mirror="$mirrordir/$project"
+mkdir -p "$project_mirror"
+project_repo_mirror="$project_mirror/repo"
+
+  # Some test URLs:
+  # 
+  # http://aimc.googlecode.com/svn/trunk/
+  # http://aimc.googlecode.com/svn/
+  # http://vagar.org/git/flam
+  # https://github.com/wslihgt/IMMF0salience.git
+  # http://hg.breakfastquay.com/dssi-vst/
+  # git://github.com/schacon/hg-git.git
+  # http://svn.drobilla.net/lad (externals!)
+
+# If we are importing from another distributed system, then our aim is
+# to create either a Hg repo or a git repo at $project_mirror, which
+# we can then pull from directly to the Hg repo at $local_repo (using
+# hg-git, in the case of a git repo).
+
+# Importing from SVN, we should use hg convert directly to the target
+# hg repo (or should we?) but keep a record of the last changeset ID
+# we brought in, and test each time whether it matches the last
+# changeset ID actually in the repo
+
+success=""
+
+if [ -d "$project_repo_mirror" ]; then
+
+    # Repo mirror exists: update it
+    echo "$$: Mirror for project $project exists at $project_repo_mirror, updating" 1>&2
+
+    if [ -d "$project_repo_mirror/.hg" ]; then
+	hg --config extensions.convert= convert --datesort "$remote_repo" "$project_repo_mirror" && success=true
+    elif [ -d "$project_repo_mirror/.git" ]; then
+	( cd "$project_repo_mirror" && git fetch "$remote_repo" ) && success=true
+    else 
+	echo "$$: ERROR: Repo mirror dir $project_repo_mirror exists but is not an Hg or git repo" 1>&2
+    fi
+
+else
+
+    # Repo mirror does not exist yet
+    echo "$$: Mirror for project $project does not yet exist at $project_repo_mirror, trying to convert or clone" 1>&2
+
+    case "$remote_repo" in
+	*git*) 
+	    git clone "$remote_repo" "$project_repo_mirror" ||
+	    hg --config extensions.convert= convert --datesort "$remote_repo" "$project_repo_mirror"
+	    ;;
+	*)
+	    hg --config extensions.convert= convert --datesort "$remote_repo" "$project_repo_mirror" ||
+	    git clone "$remote_repo" "$project_repo_mirror" ||
+	    hg clone "$remote_repo" "$project_repo_mirror"
+	    ;;
+    esac && success=true
+
+fi
+	
+echo "Success=$success"
+
+if [ -n "$success" ]; then
+    echo "$$: Update successful, pulling into local repo at $local_repo"
+    if [ -d "$project_repo_mirror/.git" ]; then
+	if [ ! -d "$local_repo" ]; then
+	    hg init "$local_repo"
+	fi
+	( cd "$local_repo" && hg --config extensions.hgext.git= pull "$project_repo_mirror" )
+    else 
+	( cd "$local_repo" && hg pull "$project_repo_mirror" )
+    fi
+fi