diff --git a/ReleaseNotes-2.7.1 b/ReleaseNotes-2.7.1
new file mode 100644
index 0000000000000000000000000000000000000000..bceb25e96d11d6fe0e8017f26d6481bb9879cd08
--- /dev/null
+++ b/ReleaseNotes-2.7.1
@@ -0,0 +1,32 @@
+#
+# openSUSE Build Service 2.7.1
+#
+
+Updaters from OBS 2.7.0 release can just ugrade the packages
+and restart all services. Updaters from former releases should
+read the README.UPDATERS file.
+
+This is in first place a bugfix release focusing on security issues
+
+Feature backports:
+==================
+
+* none
+
+Changes:
+========
+
+* none
+
+Bugfixes:
+=========
+
+* [webui][api] Update rails to version 4.2.7.1 to fix CVE-2016-6316 and CVE-2016-6317
+* [webui] Users in not 'confirmed' state were allowed to login
+
+* [api] Users in not 'confirmed' state were allowed to run services via former created token
+
+* [backend] Fixing project copy which includes binaries
+* [backend] worker supports jobs from OBS 2.8 scheduler
+* [backend] support publishing of .vdi (VirtualBox image) files
+
diff --git a/ReleaseNotes-2.7.2 b/ReleaseNotes-2.7.2
new file mode 100644
index 0000000000000000000000000000000000000000..1ca604c9731e8d978a921da64059356be62b82d0
--- /dev/null
+++ b/ReleaseNotes-2.7.2
@@ -0,0 +1,26 @@
+#
+# openSUSE Build Service 2.7.2
+#
+
+Updaters from OBS 2.7.0 release can just ugrade the packages
+and restart all services. Updaters from former releases should
+read the README.UPDATERS file.
+
+This is in first place a bugfix release focusing on security issues
+
+Feature backports:
+==================
+
+* none
+
+Changes:
+========
+
+* none
+
+Bugfixes:
+=========
+
+* [webui][api] Sets bs_request_counter correctly
+
+* [backend] bs_publish: unpublished hook added 
diff --git a/ReleaseNotes-2.7.3 b/ReleaseNotes-2.7.3
new file mode 100644
index 0000000000000000000000000000000000000000..9aa2a6e7404d80cf87ce22b01bf42d39b278c056
--- /dev/null
+++ b/ReleaseNotes-2.7.3
@@ -0,0 +1,28 @@
+#
+# openSUSE Build Service 2.7.3
+#
+
+Updaters from OBS 2.7.0 release can just ugrade the packages
+and restart all services. Updaters from former releases should
+read the README.UPDATERS file.
+
+This is in first place a bugfix release focusing on security issues
+
+Feature backports:
+==================
+
+* none
+
+Changes:
+========
+
+* Compability with OBS 2.8 remote instances
+
+Bugfixes:
+=========
+
+* [api] Project meta data was corrupted after undelete
+* [api] Raising access and sourceaccess permissions as admin is working again
+* [backend] Download on demand sync fixes
+* [webui] Fixed revert to a specified source revision
+
diff --git a/ReleaseNotes-2.7.4 b/ReleaseNotes-2.7.4
new file mode 100644
index 0000000000000000000000000000000000000000..3063f71524d98576c13a2178b499fc3db7b94036
--- /dev/null
+++ b/ReleaseNotes-2.7.4
@@ -0,0 +1,30 @@
+#
+# openSUSE Build Service 2.7.4
+#
+
+Updaters from OBS 2.7.0 release can just ugrade the packages
+and restart all services. Updaters from former releases should
+read the README.UPDATERS file.
+
+This release fixes a few bugs and a security issue caused by to loose API
+attribute permission checks.
+
+Feature backports:
+==================
+
+* none
+
+Changes:
+========
+
+* none
+
+Bugfixes:
+=========
+
+* [api] Fix API permission check for creating and changing (POST) attributes
+* [api] Fix API permission check for deleting (DELETE) attributes
+* [webui] Invalidate cached session in LDAP mode
+* [api][webui] Fail ldap authentification with empty password
+* [webui] Fix repository removal when updating project meta fails with an error
+
diff --git a/src/api/Gemfile.lock b/src/api/Gemfile.lock
index c772b0915abaed669b71b22d543bd6aeddb309f5..758e46dd49b49a075c218fa894b2f19e2ce26d52 100644
--- a/src/api/Gemfile.lock
+++ b/src/api/Gemfile.lock
@@ -1,36 +1,36 @@
 GEM
   remote: https://rubygems.org/
   specs:
-    actionmailer (4.2.5.2)
-      actionpack (= 4.2.5.2)
-      actionview (= 4.2.5.2)
-      activejob (= 4.2.5.2)
+    actionmailer (4.2.7.1)
+      actionpack (= 4.2.7.1)
+      actionview (= 4.2.7.1)
+      activejob (= 4.2.7.1)
       mail (~> 2.5, >= 2.5.4)
       rails-dom-testing (~> 1.0, >= 1.0.5)
-    actionpack (4.2.5.2)
-      actionview (= 4.2.5.2)
-      activesupport (= 4.2.5.2)
+    actionpack (4.2.7.1)
+      actionview (= 4.2.7.1)
+      activesupport (= 4.2.7.1)
       rack (~> 1.6)
       rack-test (~> 0.6.2)
       rails-dom-testing (~> 1.0, >= 1.0.5)
-      rails-html-sanitizer (~> 1.0, >= 1.0.3)
-    actionview (4.2.5.2)
-      activesupport (= 4.2.5.2)
+      rails-html-sanitizer (~> 1.0, >= 1.0.2)
+    actionview (4.2.7.1)
+      activesupport (= 4.2.7.1)
       builder (~> 3.1)
       erubis (~> 2.7.0)
       rails-dom-testing (~> 1.0, >= 1.0.5)
-      rails-html-sanitizer (~> 1.0, >= 1.0.3)
-    activejob (4.2.5.2)
-      activesupport (= 4.2.5.2)
+      rails-html-sanitizer (~> 1.0, >= 1.0.2)
+    activejob (4.2.7.1)
+      activesupport (= 4.2.7.1)
       globalid (>= 0.3.0)
-    activemodel (4.2.5.2)
-      activesupport (= 4.2.5.2)
+    activemodel (4.2.7.1)
+      activesupport (= 4.2.7.1)
       builder (~> 3.1)
-    activerecord (4.2.5.2)
-      activemodel (= 4.2.5.2)
-      activesupport (= 4.2.5.2)
+    activerecord (4.2.7.1)
+      activemodel (= 4.2.7.1)
+      activesupport (= 4.2.7.1)
       arel (~> 6.0)
-    activesupport (4.2.5.2)
+    activesupport (4.2.7.1)
       i18n (~> 0.7)
       json (~> 1.7, >= 1.7.7)
       minitest (~> 5.1)
@@ -184,16 +184,16 @@ GEM
     rack (1.6.4)
     rack-test (0.6.3)
       rack (>= 1.0)
-    rails (4.2.5.2)
-      actionmailer (= 4.2.5.2)
-      actionpack (= 4.2.5.2)
-      actionview (= 4.2.5.2)
-      activejob (= 4.2.5.2)
-      activemodel (= 4.2.5.2)
-      activerecord (= 4.2.5.2)
-      activesupport (= 4.2.5.2)
+    rails (4.2.7.1)
+      actionmailer (= 4.2.7.1)
+      actionpack (= 4.2.7.1)
+      actionview (= 4.2.7.1)
+      activejob (= 4.2.7.1)
+      activemodel (= 4.2.7.1)
+      activerecord (= 4.2.7.1)
+      activesupport (= 4.2.7.1)
       bundler (>= 1.3.0, < 2.0)
-      railties (= 4.2.5.2)
+      railties (= 4.2.7.1)
       sprockets-rails
     rails-deprecated_sanitizer (1.0.3)
       activesupport (>= 4.2.0.alpha)
@@ -205,9 +205,9 @@ GEM
       loofah (~> 2.0)
     rails_tokeninput (1.7.0)
       railties (>= 3.1.0)
-    railties (4.2.5.2)
-      actionpack (= 4.2.5.2)
-      activesupport (= 4.2.5.2)
+    railties (4.2.7.1)
+      actionpack (= 4.2.7.1)
+      activesupport (= 4.2.7.1)
       rake (>= 0.8.7)
       thor (>= 0.18.1, < 2.0)
     rainbow (2.0.0)
diff --git a/src/api/app/controllers/attribute_controller.rb b/src/api/app/controllers/attribute_controller.rb
index 40e794798c18b427b63b033bd753fa884db6b70a..5f8868ba5b979aeb405184735f66da3c1384a7ce 100644
--- a/src/api/app/controllers/attribute_controller.rb
+++ b/src/api/app/controllers/attribute_controller.rb
@@ -209,12 +209,10 @@ class AttributeController < ApplicationController
       render_error :status => 404, :errorcode => "not_found",
                    :message => "Attribute #{params[:attribute]} does not exist" and return
     end
-    if params[:attribute]
-      unless User.current.can_create_attribute_in? @attribute_container, namespace: params[:namespace], name: params[:name]
-        render_error :status => 403, :errorcode => "change_attribute_no_permission",
-                     :message => "user #{user.login} has no permission to change attribute"
-        return
-      end
+    unless User.current.can_create_attribute_in? @attribute_container, namespace: params[:namespace], name: params[:name]
+      render_error status: 403, errorcode: "change_attribute_no_permission",
+                   message: "user #{user.login} has no permission to change attribute"
+      return
     end
 
     # exec
@@ -235,26 +233,18 @@ class AttributeController < ApplicationController
     req = ActiveXML::Node.new(request.raw_post)
 
     # checks
-    if params[:attribute]
-      unless User.current.can_create_attribute_in? @attribute_container, namespace: params[:namespace], name: params[:name]
-        render_error :status => 403, :errorcode => "change_attribute_no_permission",
-                     :message => "user #{user.login} has no permission to change attribute"
+    req.each('attribute') do |attr|
+      begin
+        can_create = User.current.can_create_attribute_in? @attribute_container, namespace: attr.value('namespace'), name: attr.value('name')
+      rescue ArgumentError => e
+        render_error status: 400, errorcode: "change_attribute_attribute_error",
+                     message: e.message
         return
       end
-    else
-      req.each('attribute') do |attr|
-        begin
-          can_create = User.current.can_create_attribute_in? @attribute_container, namespace: attr.value('namespace'), name: attr.value('name')
-        rescue ArgumentError => e
-          render_error :status => 400, :errorcode => "change_attribute_attribute_error",
-                       :message => e.message
-          return
-        end
-        unless can_create
-          render_error :status => 403, :errorcode => "change_attribute_no_permission",
-                       :message => "user #{user.login} has no permission to change attribute"
-          return
-        end
+      unless can_create
+        render_error status: 403, errorcode: "change_attribute_no_permission",
+                     message: "user #{user.login} has no permission to change attribute"
+        return
       end
     end
 
diff --git a/src/api/app/controllers/request_controller.rb b/src/api/app/controllers/request_controller.rb
index 828b3be84ac3f05b1da6a9cd2a5cb15bd4ca0a36..acd1dec89a09a84c9327462d6e135a977a5175a6 100644
--- a/src/api/app/controllers/request_controller.rb
+++ b/src/api/app/controllers/request_controller.rb
@@ -59,7 +59,7 @@ class RequestController < ApplicationController
   # POST /request?cmd=create
   def global_command
     unless %w(create).include? params[:cmd]
-      raise UnknownCommandError.new "Unknown command '#{params[opt[:cmd_param]]}' for path #{request.path}"
+      raise UnknownCommandError.new "Unknown command '#{params[:cmd]}' for path #{request.path}"
     end
 
     # refuse request creation for anonymous users
diff --git a/src/api/app/controllers/source_controller.rb b/src/api/app/controllers/source_controller.rb
index 56f4f9071ee9f08de29bd40de33e4a814e5834c3..2a2150b1987b858274fb472036c7d7c9d8016e64 100644
--- a/src/api/app/controllers/source_controller.rb
+++ b/src/api/app/controllers/source_controller.rb
@@ -150,7 +150,11 @@ class SourceController < ApplicationController
       return
     end
     project.check_weak_dependencies!
-    check_and_remove_repositories!(project.repositories, !params[:remove_linking_repositories].blank?, !params[:force].blank?)
+    opts = { no_write_to_backend: true,
+             force:               params[:force].present?,
+             recursive_remove:    params[:remove_linking_repositories].present?
+           }
+    check_and_remove_repositories!(project.repositories, opts)
 
     logger.info "destroying project object #{project.name}"
     project.commit_opts = { comment: params[:comment] }
@@ -504,7 +508,11 @@ class SourceController < ApplicationController
 
     if project
       remove_repositories = project.get_removed_repositories(request_data)
-      check_and_remove_repositories!(remove_repositories, !params[:remove_linking_repositories].blank?, !params[:force].blank?)
+      opts = { no_write_to_backend: true,
+               force:               params[:force].present?,
+               recursive_remove:    params[:remove_linking_repositories].present?
+             }
+      check_and_remove_repositories!(remove_repositories, opts)
     end
 
     Project.transaction do
@@ -522,13 +530,14 @@ class SourceController < ApplicationController
     render_ok
   end
 
-  def check_and_remove_repositories!(repositories, full_remove, force = false)
-    error = Project.check_repositories(repositories) unless force
-    if !force && error[:error]
+  def check_and_remove_repositories!(repositories, opts)
+    error = Project.check_repositories(repositories) unless opts[:force]
+    if !opts[:force] && error[:error]
       raise RepoDependency, error[:error]
     else
-      error = Project.remove_repositories(repositories, full_remove)
-      if !force && error[:error]
+      error = Project.remove_repositories(repositories, opts)
+
+      if !opts[:force] && error[:error]
         raise ChangeProjectNoPermission, error[:error]
       end
     end
@@ -689,10 +698,10 @@ class SourceController < ApplicationController
         return
       end
 
-      if pkg and not pkg.disabled_for?('sourceaccess', nil, nil)
-        if FlagHelper.xml_disabled_for?(rdata, 'sourceaccess')
-          render_error :status => 403, :errorcode => 'change_package_protection_level',
-                       :message => 'admin rights are required to raise the protection level of a package'
+      if pkg && !pkg.disabled_for?('sourceaccess', nil, nil)
+        if FlagHelper.xml_disabled_for?(rdata, 'sourceaccess') && !User.current.is_admin?
+          render_error status: 403, errorcode: 'change_package_protection_level',
+                       message: 'admin rights are required to raise the protection level of a package'
           return
         end
       end
diff --git a/src/api/app/controllers/trigger_controller.rb b/src/api/app/controllers/trigger_controller.rb
index ca7c6a7b79f16bc75ac1c1b8ee094da81275dda5..d7a060e874eda0920d7070b5379d136606566e9c 100644
--- a/src/api/app/controllers/trigger_controller.rb
+++ b/src/api/app/controllers/trigger_controller.rb
@@ -27,10 +27,17 @@ class TriggerController < ApplicationController
     end
 
     pkg = token.package
+    if pkg
+      # check if user has still access
+      unless token.user.is_active? && token.user.can_modify_package?(pkg)
+        raise NoPermission.new "no permission for package #{pkg.name} in project #{pkg.project.name}"
+      end
+    end
+
     unless pkg
       # token is not bound to a package, but event may have specified it
       pkg = Package.get_by_project_and_name(params[:project].to_s, params[:package].to_s, use_source: true)
-      unless token.user.can_modify_package? pkg
+      unless token.user.is_active? && token.user.can_modify_package?(pkg)
 	raise NoPermission.new "no permission for package #{pkg.name} in project #{pkg.project.name}"
       end
     end
diff --git a/src/api/app/controllers/webui/package_controller.rb b/src/api/app/controllers/webui/package_controller.rb
index df92562dd0704843c2458cd28cdb27f34a3b9625..f6f3aa85f0b1c1a80ee559e29a1cf544a3b0ed7e 100644
--- a/src/api/app/controllers/webui/package_controller.rb
+++ b/src/api/app/controllers/webui/package_controller.rb
@@ -278,6 +278,7 @@ class Webui::PackageController < Webui::WebuiController
         elsif params[:project].include?(':branches:')
           opts[:sourceupdate] = 'update' # Avoid auto-removal of branch
         end
+        opts[:source_rev] = params[:rev] if params[:rev]
         action = BsRequestActionSubmit.new(opts)
         req.bs_request_actions << action
         action.bs_request = req
diff --git a/src/api/app/controllers/webui/project_controller.rb b/src/api/app/controllers/webui/project_controller.rb
index 9a02c40cee63487ec514b40495f55f5a1a614738..b9771404ddc4a0be13dc8ea8a095d0358fa24793 100644
--- a/src/api/app/controllers/webui/project_controller.rb
+++ b/src/api/app/controllers/webui/project_controller.rb
@@ -708,12 +708,13 @@ class Webui::ProjectController < Webui::WebuiController
       Suse::Validator.validate('project', params[:meta])
       request_data = Xmlhash.parse(params[:meta])
 
+      remove_repositories = @project.get_removed_repositories(request_data)
+      errors << Project.check_repositories(remove_repositories)[:error]
       errors << Project.validate_remote_permissions(request_data)[:error]
       errors << Project.validate_link_xml_attribute(request_data, @project.name)[:error]
       errors << Project.validate_maintenance_xml_attribute(request_data)[:error]
       errors << Project.validate_repository_xml_attribute(request_data, @project.name)[:error]
 
-      errors << @project.check_and_remove_repositories(request_data, !params[:remove_linking_repositories].blank?)[:error]
       errors = errors.compact
 
       if errors.empty?
@@ -724,7 +725,7 @@ class Webui::ProjectController < Webui::WebuiController
         end
       end
 
-    rescue Suse::ValidationError  => exception
+    rescue Suse::ValidationError => exception
       errors << exception.message
     rescue Project::UnknownObjectError  => exception
       errors << "Project with name '#{exception.message}' not found"
diff --git a/src/api/app/controllers/webui/user_controller.rb b/src/api/app/controllers/webui/user_controller.rb
index 5127ca8b3c05e0976e6b2871b27f74a2f1dfbb15..9505fb8e41acbb3bac840c8c9e5e5803c84e8408 100644
--- a/src/api/app/controllers/webui/user_controller.rb
+++ b/src/api/app/controllers/webui/user_controller.rb
@@ -14,6 +14,7 @@ class Webui::UserController < Webui::WebuiController
 
   def logout
     logger.info "Logging out: #{session[:login]}"
+    Rails.cache.delete("ldap_cache_userpasswd:#{session[:login]}")
     reset_session
     User.current = nil
     if CONFIG['proxy_auth_mode'] == :on
@@ -27,12 +28,22 @@ class Webui::UserController < Webui::WebuiController
   end
 
   def do_login
-    unless User.authenticate(params[:username], params[:password])
+    user = User.find_with_credentials(params[:username], params[:password])
+
+    if user && !user.is_active?
+      redirect_to(root_path, error: "Your account is disabled. Please contact the adminsitrator for details.")
+      return
+    end
+
+    unless user
       redirect_to(user_login_path, error: 'Authentication failed')
       return
     end
 
-    session[:login] = User.current.login
+    Rails.logger.debug "Authentificated user '#{user.try(:login)}'"
+
+    session[:login] = user.login
+    User.current = user
 
     if request.referer.end_with?("/user/login")
       redirect_to home_path
diff --git a/src/api/app/controllers/webui/webui_controller.rb b/src/api/app/controllers/webui/webui_controller.rb
index e65db45dd8066dccf7ac03af1fc95017a3f395a6..8d0468b518c2eee716b9d125755b07d1b0c6723f 100644
--- a/src/api/app/controllers/webui/webui_controller.rb
+++ b/src/api/app/controllers/webui/webui_controller.rb
@@ -211,7 +211,7 @@ class Webui::WebuiController < ActionController::Base
         return
       end
 
-      # If the user logs in for the first time (before we have a User with that login)
+      # The user does not exist in our database, create her.
       unless User.where(login: user_login).exists?
         logger.debug "Creating user #{user_login}"
         chars = ["A".."Z", "a".."z", "0".."9"].collect { |r| r.to_a }.join
@@ -224,9 +224,15 @@ class Webui::WebuiController < ActionController::Base
                      password_confirmation: fakepw)
       end
 
-      User.current = User.find_by_login(user_login)
+      # The user exists, check if shes active and update the info
+      User.current = User.find_by(login: user_login)
+      unless User.current.is_active?
+        session[:login] = nil
+        User.current = User.find_nobody!
+        redirect_to(CONFIG['proxy_auth_logout_page'], error: "Your account is disabled. Please contact the adminsitrator for details.")
+        return
+      end
       User.current.update_user_info_from_proxy_env(request.env)
-      return
     end
 
     User.current = User.find_by_login(session[:login]) if session[:login]
diff --git a/src/api/app/models/bs_request_action_maintenance_incident.rb b/src/api/app/models/bs_request_action_maintenance_incident.rb
index dee3ff3f01b0ca5bd1bc1c8930ebbe5ff41d24b5..2a664a5652fe23783d3b0d6a04b825c68ee28793 100644
--- a/src/api/app/models/bs_request_action_maintenance_incident.rb
+++ b/src/api/app/models/bs_request_action_maintenance_incident.rb
@@ -58,7 +58,7 @@ class BsRequestActionMaintenanceIncident < BsRequestAction
     super(opts)
   end
 
-  def _merge_pkg_into_maintenance_incident(incidentProject, source_project, source_package, releaseproject = nil, request = nil)
+  def _merge_pkg_into_maintenance_incident(incidentProject)
     # recreate package based on link target and throw everything away, except source changes
     # silently as maintenance teams requests ...
     new_pkg = nil
@@ -86,17 +86,17 @@ class BsRequestActionMaintenanceIncident < BsRequestAction
       end
 
       # use specified release project if defined
-    elsif releaseproject
+    elsif target_releaseproject
       package_name = source_package
       package_name = linkinfo['package'] if linkinfo
 
-      branch_params = {:target_project => incidentProject.name,
-                       :olinkrev => 'base',
-                       :maintenance => 1,
-                       :force => 1,
-                       :comment => 'Initial new branch',
-                       :project => releaseproject, :package => package_name}
-      branch_params[:requestid] = request.id if request
+      branch_params = {target_project: incidentProject.name,
+                       olinkrev: 'base',
+                       requestid: bs_request.number,
+                       maintenance: 1,
+                       force: 1,
+                       comment: 'Initial new branch',
+                       project: target_releaseproject, package: package_name}
       # accept branching from former update incidents or GM (for kgraft case)
       linkprj = Project.find_by_name(linkinfo['project']) if linkinfo
       if defined?(linkprj) && linkprj
@@ -117,13 +117,12 @@ class BsRequestActionMaintenanceIncident < BsRequestAction
       # linked to an existing package in an external project
       linked_project = linkinfo['project']
       linked_package = linkinfo['package']
-
-      branch_params = {:target_project => incidentProject.name,
-                       :olinkrev => 'base',
-                       :maintenance => 1,
-                       :force => 1,
-                       :project => linked_project, :package => linked_package}
-      branch_params[:requestid] = request.id if request
+      branch_params = {target_project: incidentProject.name,
+                       olinkrev: 'base',
+                       requestid: bs_request.number,
+                       maintenance: 1,
+                       force: 1,
+                       project: linked_project, package: linked_package}
       ret = BranchPackage.new(branch_params).branch
       new_pkg = Package.get_by_project_and_name(ret[:data][:targetproject], ret[:data][:targetpackage])
     else
@@ -142,21 +141,22 @@ class BsRequestActionMaintenanceIncident < BsRequestAction
       end
     end
 
-    # backend copy of current sources, but keep link
+    # backend copy of submitted sources, but keep link
     cp_params = {
       cmd:            "copy",
       user:           User.current.login,
       oproject:       source_project,
       opackage:       source_package,
+      requestid:      bs_request.number,
       keeplink:       1,
       expand:         1,
       withacceptinfo: 1,
       comment:        "Maintenance incident copy from project #{source_project}"
     }
-    cp_params[:requestid] = request.number if request
+    cp_params[:orev] = source_rev if source_rev
     cp_path = "/source/#{CGI.escape(incidentProject.name)}/#{CGI.escape(new_pkg.name)}"
     cp_path << Suse::Backend.build_query_from_hash(cp_params, [:cmd, :user, :oproject, :opackage,
-                                                               :keeplink, :expand, :comment,
+                                                               :orev, :keeplink, :expand, :comment,
                                                                :requestid, :withacceptinfo])
     result = Suse::Backend.post cp_path, nil
     result = Xmlhash.parse(result.body)
@@ -166,10 +166,10 @@ class BsRequestActionMaintenanceIncident < BsRequestAction
     new_pkg
   end
 
-  def merge_into_maintenance_incident(incidentProject, source_project, source_package, releaseproject = nil, request = nil)
+  def merge_into_maintenance_incident(incidentProject)
     # copy all or selected packages and project source files from base project
     # we don't branch from it to keep the link target.
-    pkg = _merge_pkg_into_maintenance_incident(incidentProject, source_project, source_package, releaseproject, request)
+    pkg = _merge_pkg_into_maintenance_incident(incidentProject)
 
     incidentProject.save!
     incidentProject.store(comment: "maintenance_incident request #{self.bs_request.number}", request: self.bs_request)
@@ -181,9 +181,7 @@ class BsRequestActionMaintenanceIncident < BsRequestAction
     incident_project = Project.get_by_name(self.target_project)
 
     # the incident got created before
-    self.target_package = merge_into_maintenance_incident(incident_project,
-                                                          self.source_project, self.source_package,
-                                                          self.target_releaseproject, self.bs_request)
+    self.target_package = merge_into_maintenance_incident(incident_project)
 
     # update action with real target project
     self.target_project = incident_project.name
diff --git a/src/api/app/models/bs_request_permission_check.rb b/src/api/app/models/bs_request_permission_check.rb
index 721553f2cedd6d675a9be163fe504fefeb9ff660..f644a316c9a8d50ad8b3e1c4da3a550ab584a255 100644
--- a/src/api/app/models/bs_request_permission_check.rb
+++ b/src/api/app/models/bs_request_permission_check.rb
@@ -171,7 +171,7 @@ class BsRequestPermissionCheck
     end
   end
 
-  def set_permissions_for_action(action)
+  def set_permissions_for_action(action, new_state = nil)
     # general write permission check on the target on accept
     @write_permission_in_this_action = false
 
@@ -192,6 +192,12 @@ class BsRequestPermissionCheck
       end
     end
 
+    if action.action_type == :maintenance_incident
+      # this action type is always branching using extended names
+      target_package_name = Package.extended_name(action.source_project, action.source_package)
+      @target_package = @target_project.packages.find_by_name(target_package_name)
+    end
+
     # general source write permission check (for revoke)
     if (@source_package and User.current.can_modify_package?(@source_package, true)) or
         (not @source_package and @source_project and User.current.can_modify_project?(@source_project, true))
@@ -201,7 +207,8 @@ class BsRequestPermissionCheck
     # general write permission check on the target on accept
     @write_permission_in_this_action = false
     # meta data change shall also be allowed after freezing a project using force:
-    ignoreLock = opts[:force] and [:set_bugowner].include? action.action_type
+    ignoreLock = (new_state == "declined") ||
+        (opts[:force] && action.action_type == :set_bugowner)
     if @target_package
       if User.current.can_modify_package?(@target_package, ignoreLock)
         @write_permission_in_target = true
@@ -349,7 +356,7 @@ class BsRequestPermissionCheck
 
     # permission and validation check for each action inside
     req.bs_request_actions.each do |action|
-      set_permissions_for_action(action)
+      set_permissions_for_action(action, opts[:newstate])
 
       check_newstate_action! action, opts
 
diff --git a/src/api/app/models/package.rb b/src/api/app/models/package.rb
index f2fb142438c2eedc822f60b3378b1587986e38b6..99b024a82e70b4f0d71726fdf1c979a76172de94 100644
--- a/src/api/app/models/package.rb
+++ b/src/api/app/models/package.rb
@@ -839,6 +839,14 @@ class Package < ActiveRecord::Base
     return BsRequest.where(id: rel.pluck('bs_requests.id'))
   end
 
+  def self.extended_name(project, package)
+    # the package name which will be used on a branch with extended or maintenance option
+    directory_hash = Directory.hashed(project: project, package: package)
+    linkinfo = directory_hash["linkinfo"] || {}
+
+    "#{linkinfo['package'] || package}.#{linkinfo['project'] || project}".gsub(/:/, '_')
+  end
+
   def linkinfo
     dir_hash['linkinfo']
   end
diff --git a/src/api/app/models/project.rb b/src/api/app/models/project.rb
index 389cc02729f532caa1e29b011b65c1383baa89d8..bfcb1a145a349ed0fd0b33279ec477b3e8a80ee6 100644
--- a/src/api/app/models/project.rb
+++ b/src/api/app/models/project.rb
@@ -527,18 +527,18 @@ class Project < ActiveRecord::Base
 
     # check for raising read access permissions, which can't get ensured atm
     unless self.new_record? || self.disabled_for?('access', nil, nil)
-      if FlagHelper.xml_disabled_for?(xmlhash, 'access')
+      if FlagHelper.xml_disabled_for?(xmlhash, 'access') && !User.current.is_admin?
         raise ForbiddenError.new
       end
     end
     unless self.new_record? || self.disabled_for?('sourceaccess', nil, nil)
-      if FlagHelper.xml_disabled_for?(xmlhash, 'sourceaccess')
+      if FlagHelper.xml_disabled_for?(xmlhash, 'sourceaccess') && !User.current.is_admin?
         raise ForbiddenError.new
       end
     end
     new_record = self.new_record?
-    if ::Configuration.default_access_disabled == true and not new_record
-      if self.disabled_for?('access', nil, nil) and not FlagHelper.xml_disabled_for?(xmlhash, 'access')
+    if ::Configuration.default_access_disabled == true && !new_record
+      if self.disabled_for?('access', nil, nil) && !FlagHelper.xml_disabled_for?(xmlhash, 'access') && !User.current.is_admin?
         raise ForbiddenError.new
       end
     end
@@ -1728,16 +1728,6 @@ class Project < ActiveRecord::Base
     {}
   end
 
-  def check_and_remove_repositories(request_data, full_remove = false)
-    remove_repositories = get_removed_repositories(request_data)
-    error = Project.check_repositories(remove_repositories)
-
-    return error if error[:error]
-
-    error = Project.remove_repositories(remove_repositories, full_remove)
-    error[:error] ? error : {}
-  end
-
   def get_removed_repositories(request_data)
     new_repositories = request_data.elements('repository').map(&:values).flatten
     old_repositories = repositories.all.map(&:name)
@@ -1773,7 +1763,8 @@ class Project < ActiveRecord::Base
     {}
   end
 
-  def self.remove_repositories(repositories, full_remove = false)
+  # opts: recursive_remove no_write_to_backend
+  def self.remove_repositories(repositories, opts = {})
     deleted_repository = Repository.deleted_instance
 
     repositories.each do |repo|
@@ -1781,10 +1772,13 @@ class Project < ActiveRecord::Base
       project = repo.project
 
       # full remove, otherwise the model will take care of the cleanup
-      if full_remove
+      if opts[:recursive_remove]
         # recursive for INDIRECT linked repositories
         unless linking_repositories.length < 1
-          Project.remove_repositories(linking_repositories, true)
+          # FIXME: we would actually need to check for :no_write_to_backend here as well
+          #        but the calling code is currently broken and would need the starting
+          #        project different
+          Project.remove_repositories(linking_repositories, {recursive_remove: true})
         end
 
         # try to remove the repository
@@ -1802,7 +1796,7 @@ class Project < ActiveRecord::Base
       if Repository.exists?(repo.id) && repository
         logger.info "destroy repo #{repository.name} in '#{project.name}'"
         repository.destroy
-        project.store({ lowprio: true }) # low prio storage
+        project.store({ lowprio: true }) unless opts[:no_write_to_backend]
       end
     end
     {}
diff --git a/src/api/app/models/token.rb b/src/api/app/models/token.rb
index 6fea6218b8d91c6a8f39043173f4e840c0c1f97e..040faecf89cc7c26bb124dbd85b6faab5a939468 100644
--- a/src/api/app/models/token.rb
+++ b/src/api/app/models/token.rb
@@ -8,11 +8,6 @@ class Token < ActiveRecord::Base
   def self.find_by_string(token)
     token = Token.where(string: token.to_s).includes(:package, :user).first
     return nil unless token and token.user_id
-    # is token bound to a package?
-    if token.package
-      # check if user has still access
-      return nil unless token.user.can_modify_package? token.package
-    end
 
     # package found and user has write access
     return token
diff --git a/src/api/app/models/user_ldap_strategy.rb b/src/api/app/models/user_ldap_strategy.rb
index b346ff0bc91d9f3d2d25eb27ba827eae84c665f7..81176f710629727892781af83f8bf234481108a4 100644
--- a/src/api/app/models/user_ldap_strategy.rb
+++ b/src/api/app/models/user_ldap_strategy.rb
@@ -452,6 +452,9 @@ class UserLdapStrategy
         return nil
       end
     when :ldap then
+      # ruby-ldap returns true if password is empty
+      # https://github.com/ruby-ldap/ruby-net-ldap/issues/5
+      return unless password.present?
       # Don't match the passwd locally, try to bind to the ldap server
       user_con= initialize_ldap_con(user['dn'], password)
       if user_con.nil?
diff --git a/src/api/config/options.yml.example b/src/api/config/options.yml.example
index 045b040248cc4209e050d3fe02cf328a3a6cbb30..3ae0a47f154f253e9343d891d205bf15b9714d0b 100644
--- a/src/api/config/options.yml.example
+++ b/src/api/config/options.yml.example
@@ -62,7 +62,9 @@ proxy_auth_test_email: coolguy@example.com
 # LDAP options
 ##################
 
+#### WARNING: LDAP mode is not official supported by OBS!
 ldap_mode: :off
+#### WARNING: LDAP mode is not official supported by OBS!
 
 # LDAP Servers separated by ':'.
 # OVERRIDE with your company's ldap servers. Servers are picked randomly for
diff --git a/src/api/db/migrate/20160824132643_fix_bs_request_counter.rb b/src/api/db/migrate/20160824132643_fix_bs_request_counter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..25dbd6d38502a3f593b1461f24b82785560f600b
--- /dev/null
+++ b/src/api/db/migrate/20160824132643_fix_bs_request_counter.rb
@@ -0,0 +1,18 @@
+class FixBsRequestCounter < ActiveRecord::Migration
+  class TempBsRequest < ActiveRecord::Base
+    self.table_name = 'bs_requests'
+  end
+
+  class TempBsRequestCounter < ActiveRecord::Base
+    self.table_name = 'bs_request_counter'
+  end
+
+  def change
+    # BsRequestCounter is not set correctly
+    # Introduced with 20160321105300_request_counter.rb
+    # See https://github.com/openSUSE/open-build-service/issues/2068
+    counter = TempBsRequest.reorder(:number).pluck(:number).last || 0
+    TempBsRequestCounter.destroy_all
+    TempBsRequestCounter.create!(counter: counter + 1)
+  end
+end
diff --git a/src/api/db/structure.sql b/src/api/db/structure.sql
index 0222b2021ea0f77cac4e65c0a3c182ba06b0a8b0..88a174effeb9c0e682aa277420fbca403c315b7f 100644
--- a/src/api/db/structure.sql
+++ b/src/api/db/structure.sql
@@ -1680,6 +1680,8 @@ INSERT INTO schema_migrations (version) VALUES ('20160321105300');
 
 INSERT INTO schema_migrations (version) VALUES ('20160518105300');
 
+INSERT INTO schema_migrations (version) VALUES ('20160824132643');
+
 INSERT INTO schema_migrations (version) VALUES ('21');
 
 INSERT INTO schema_migrations (version) VALUES ('22');
diff --git a/src/api/spec/controllers/webui/user_controller_spec.rb b/src/api/spec/controllers/webui/user_controller_spec.rb
index a14d97910bfc6ad310b625b8aadc622abacc21f1..4a9fe52879373f9e309df4dad0908c63b130891e 100644
--- a/src/api/spec/controllers/webui/user_controller_spec.rb
+++ b/src/api/spec/controllers/webui/user_controller_spec.rb
@@ -70,10 +70,31 @@ RSpec.describe Webui::UserController do
   describe "POST #do_login" do
     before do
       request.env["HTTP_REFERER"] = search_url # Needed for the redirect_to :back
+    end
+
+    it 'logs in users with correct credentials' do
       post :do_login, {username: user.login, password: 'buildservice'}
+      expect(response).to redirect_to search_url
     end
 
-    it { expect(response).to redirect_to search_url }
+    it 'tells users about wrong credentials' do
+      post :do_login, {username: user.login, password: 'password123'}
+      expect(response).to redirect_to user_login_path
+      expect(flash[:error]).to eq("Authentication failed")
+    end
+
+    it 'tells users about wrong state' do
+      user.update_attribute('state', User::STATES['locked'])
+      post :do_login, {username: user.login, password: 'buildservice'}
+      expect(response).to redirect_to root_path
+      expect(flash[:error]).to eq("Your account is disabled. Please contact the adminsitrator for details.")
+    end
+
+    it 'assigns the current user' do
+      post :do_login, {username: user.login, password: 'buildservice'}
+      expect(User.current).to eq(user)
+      expect(session[:login]).to eq(user.login)
+    end
   end
 
   describe "GET #home" do
diff --git a/src/api/test/fixtures/path_elements.yml b/src/api/test/fixtures/path_elements.yml
index fc0f08989a5b0ec4898a090f9ea66202ae01a873..d6b16dc3373dcb37fbcebcf86dba7707e9d21623 100644
--- a/src/api/test/fixtures/path_elements.yml
+++ b/src/api/test/fixtures/path_elements.yml
@@ -30,3 +30,7 @@ UseRemoteInstance_pop_path:
   parent_id: BaseDistro_repo
   repository_id: UseRemoteInstance_pop
   position: 1
+kde4_repo:
+  parent_id: 98
+  repository_id: 86
+  position: 1
diff --git a/src/api/test/fixtures/repositories.yml b/src/api/test/fixtures/repositories.yml
index e1713c8cf2f041625f27c30dc23c31438ba1e1cc..de44fa8f664cb59f10f9196907b085bc11539814 100644
--- a/src/api/test/fixtures/repositories.yml
+++ b/src/api/test/fixtures/repositories.yml
@@ -84,3 +84,7 @@ repositories_97:
   id: 97
   name: BrokenPublishing_repo
   project: BrokenPublishing
+kde4_standard:
+  id: 98
+  name: kde4_standard
+  project: kde4
diff --git a/src/api/test/fixtures/repository_architectures.yml b/src/api/test/fixtures/repository_architectures.yml
index 24895e429ce3103fd2a2389f0b34d21fd8bd43cb..ec912acec4ed1a9dfa39c7f92e887860bfde950e 100644
--- a/src/api/test/fixtures/repository_architectures.yml
+++ b/src/api/test/fixtures/repository_architectures.yml
@@ -122,3 +122,8 @@ repository_architectures_broken_publishing:
   position: 0
   id: 940713216
   architecture: i586
+kde4_repo_arch:
+  repository_id: 98
+  position: 0
+  id: 940713217
+  architecture: s390
diff --git a/src/api/test/functional/attributes_test.rb b/src/api/test/functional/attributes_test.rb
index e2271faf35c6abfef993201e910b177900a958b3..c0ff74b74ff974dd4cedda295257c7963b867557 100644
--- a/src/api/test/functional/attributes_test.rb
+++ b/src/api/test/functional/attributes_test.rb
@@ -260,6 +260,33 @@ ription</description>
     assert_response 404
   end
 
+  def test_attrib_write_permissions
+    login_tom
+
+    data = "<attributes><attribute namespace='OBS' name='VeryImportantProject'/></attributes>"
+
+    # XML with an attribute I should not be able to create
+    post "/source/home:tom/_attribute", data
+    assert_response 403
+    # same with attribute parameter
+    post "/source/home:tom/_attribute/OBS:Issues", data
+    assert_response 403
+  end
+
+  def test_attrib_delete_permissions
+    # create an admin only attribute
+    login_king
+    data = "<attributes><attribute namespace='OBS' name='VeryImportantProject'/></attributes>"
+    post "/source/home:tom/_attribute", data
+    assert_response :success
+
+    login_tom
+    delete "/source/home:tom/_attribute/OBS:VeryImportantProject"
+    assert_response 403
+    delete "/source/home:tom/_attribute/?namespace=OBS&name=VeryImportantProject"
+    assert_response 403
+  end
+
   def test_create_attributes_project
     login_tom
 
diff --git a/src/api/test/functional/channel_maintenance_test.rb b/src/api/test/functional/channel_maintenance_test.rb
index 4a42c19eaafc2bc06aeab720e8dd61d38632b7dd..25a14f511b10870afa4d6f645e835867b1419a79 100644
--- a/src/api/test/functional/channel_maintenance_test.rb
+++ b/src/api/test/functional/channel_maintenance_test.rb
@@ -152,7 +152,8 @@ class ChannelMaintenanceTests < ActionDispatch::IntegrationTest
     assert_response :success
 
     # create maintenance request with invalid target
-    post '/request?cmd=create', '<request>
+    login_tom
+    post '/request?cmd=create&addrevision=1', '<request>
                                    <action type="maintenance_incident">
                                      <source project="home:tom:branches:OBS_Maintained:pack2" package="pack2.BaseDistro2.0_LinkedUpdateProject" />
                                      <target project="home:tom" />
@@ -161,7 +162,7 @@ class ChannelMaintenanceTests < ActionDispatch::IntegrationTest
     assert_response 400
     assert_xml_tag :tag => 'status', :attributes => { code: 'no_maintenance_project' }
     # valid target..
-    post '/request?cmd=create', '<request>
+    post '/request?cmd=create&addrevision=1', '<request>
                                    <action type="maintenance_incident">
                                      <source project="home:tom:branches:OBS_Maintained:pack2" package="pack2.BaseDistro2.0_LinkedUpdateProject" />
                                      <target project="'+incidentProject+'" />
@@ -178,7 +179,7 @@ class ChannelMaintenanceTests < ActionDispatch::IntegrationTest
 
     # create maintenance request for two further packages
     # without specifing target, the default target must get found via attribute
-    post '/request?cmd=create', '<request>
+    post '/request?cmd=create&addrevision=1', '<request>
                                    <action type="maintenance_incident">
                                      <source project="home:tom:branches:OBS_Maintained:pack2" package="pack2.BaseDistro2.0_LinkedUpdateProject" />
                                    </action>
@@ -193,7 +194,7 @@ class ChannelMaintenanceTests < ActionDispatch::IntegrationTest
     node = ActiveXML::Node.new(@response.body)
     assert node.has_attribute?(:id)
     id2 = node.value(:id)
-    post '/request?cmd=create', '<request>
+    post '/request?cmd=create&addrevision=1', '<request>
                                    <action type="maintenance_incident">
                                      <source project="home:tom:branches:OBS_Maintained:pack2" package="pack2.BaseDistro2.0_LinkedUpdateProject" />
                                    </action>
@@ -540,7 +541,8 @@ class ChannelMaintenanceTests < ActionDispatch::IntegrationTest
     node = ActiveXML::Node.new(@response.body)
     assert node.has_attribute?(:id)
     reqid = node.value(:id)
-    # revoke try new request
+
+    # revoke the release request request
     post "/request/#{reqid}?cmd=changestate&newstate=revoked"
     assert_response :success
 
diff --git a/src/api/test/functional/maintenance_test.rb b/src/api/test/functional/maintenance_test.rb
index bc170b45c442e35d13b9dedab5d93cd6c0584727..45b4444e8dfe759be3a0428805905bd637ef1484 100644
--- a/src/api/test/functional/maintenance_test.rb
+++ b/src/api/test/functional/maintenance_test.rb
@@ -206,7 +206,7 @@ class MaintenanceTests < ActionDispatch::IntegrationTest
                                    <state name="new" />
                                  </request>'
     assert_response :success
-    post '/request?cmd=create', '<request>
+    post '/request?cmd=create&addrevision=1', '<request>
                                    <action type="maintenance_incident">
                                      <source project="RemoteInstance:kde4" package="kdelibs" />
                                      <target project="My:Maintenance" releaseproject="BaseDistro2.0:LinkedUpdateProject" />
@@ -220,6 +220,12 @@ class MaintenanceTests < ActionDispatch::IntegrationTest
     assert node.has_attribute?(:id)
     id1 = node.value(:id)
 
+    # modify source afterwards, must not appear in target after accept
+    login_king
+    put "/source/kde4/kdelibs/TEMP_FILE", "dummy"
+    assert_response :success
+    login_tom
+
     # validate that request is diffable (not broken)
     post "/request/#{id1}?cmd=diff&view=xml", nil
     assert_response :success
@@ -245,7 +251,9 @@ class MaintenanceTests < ActionDispatch::IntegrationTest
 
     get "/source/#{incidentProject}/kdelibs.BaseDistro2.0_LinkedUpdateProject"
     assert_response :success
-    assert_xml_tag( :tag => 'linkinfo', :attributes => { project: 'BaseDistro2.0:LinkedUpdateProject', package: 'kdelibs' } )
+    assert_xml_tag( tag: 'linkinfo', attributes: { project: 'BaseDistro2.0:LinkedUpdateProject', package: 'kdelibs' } )
+    get "/source/#{incidentProject}/kdelibs.BaseDistro2.0_LinkedUpdateProject/TEMP_FILE"
+    assert_response 404
 
     # no patchinfo was part in source project, got it created ?
     get "/source/#{incidentProject}/patchinfo/_patchinfo"
@@ -338,6 +346,8 @@ class MaintenanceTests < ActionDispatch::IntegrationTest
     assert_response :success
     delete '/source/BaseDistro2.0:LinkedUpdateProject/kdelibs'
     assert_response :success
+    delete "/source/kde4/kdelibs/TEMP_FILE"
+    assert_response :success
   end
 
   def test_OBS_BranchTarget
@@ -599,7 +609,10 @@ class MaintenanceTests < ActionDispatch::IntegrationTest
     assert_no_xml_tag :parent => { tag: 'issue' }, :tag => 'issue', :attributes => { change: '' }
     assert_xml_tag :parent => { tag: 'issue', attributes: { change: 'added' } }, :tag => 'name', :content => '1042'
 
-    post '/source', :cmd => 'branch', :package => 'kdelibs', :target_project => 'home:tom:branches:OBS_Maintained:pack2'
+    get '/source/home:tom:branches:OBS_Maintained:pack2/_meta'
+    assert_response :success
+    oldmeta = @response.body
+    post '/source', cmd: 'branch', package: 'kdelibs', target_project: 'home:tom:branches:OBS_Maintained:pack2'
     assert_response :success
     get '/source/home:tom:branches:OBS_Maintained:pack2/kdelibs.kde4/_link'
     assert_response :success
@@ -658,6 +671,8 @@ class MaintenanceTests < ActionDispatch::IntegrationTest
     # delete kdelibs package again or incident creation will fail since it does not point to a maintained project.
     delete '/source/home:tom:branches:OBS_Maintained:pack2/kdelibs.kde4'
     assert_response :success
+    put '/source/home:tom:branches:OBS_Maintained:pack2/_meta', oldmeta
+    assert_response :success
 
     # create maintenance request
     # without specifing target, the default target must get found via attribute
@@ -689,8 +704,8 @@ class MaintenanceTests < ActionDispatch::IntegrationTest
 
     # store data for later checks
     get '/source/home:tom:branches:OBS_Maintained:pack2/_meta'
-    oprojectmeta = ActiveXML::Node.new(@response.body)
     assert_response :success
+    oprojectmeta = ActiveXML::Node.new(@response.body)
 
     get "/source/home:tom:branches:OBS_Maintained:pack2/_meta"
     assert_response :success
diff --git a/src/api/test/functional/read_permission_test.rb b/src/api/test/functional/read_permission_test.rb
index 0859a0ce496381416a7d820f63c9a35f85b40c60..3e2c4db37b515ca487efd3363a22e157cc3491d9 100644
--- a/src/api/test/functional/read_permission_test.rb
+++ b/src/api/test/functional/read_permission_test.rb
@@ -502,7 +502,12 @@ class ReadPermissionTest < ActionDispatch::IntegrationTest
     put url_for(:controller => :source, :action => :update_package_meta, :project => "home:adrian:PublicProject", :package => "pack"),
         '<package name="pack" project="home:adrian:PublicProject"> <title/> <description/>  <sourceaccess><disable/></sourceaccess>  </package>'
     assert_response 403
-    assert_xml_tag :tag => "status", :attributes => { :code => "change_package_protection_level" }
+    assert_xml_tag tag: "status", attributes: { code: "change_package_protection_level" }
+    # but works as admin
+    login_king
+    put url_for(controller: :source, action: :update_package_meta, project: "home:adrian:PublicProject", package: "pack"),
+        '<package name="pack" project="home:adrian:PublicProject"> <title/> <description/>  <sourceaccess><disable/></sourceaccess>  </package>'
+    assert_response :success
     delete "/source/home:adrian:Project"
     assert_response :success
     delete "/source/home:adrian:PublicProject"
@@ -518,7 +523,11 @@ class ReadPermissionTest < ActionDispatch::IntegrationTest
     put url_for(:controller => :source, :action => :update_project_meta, :project => "home:adrian:Project"),
         '<project name="home:adrian:Project"> <title/> <description/> <access><disable/></access> </project>'
     assert_response 403
-    assert_xml_tag :tag => "status", :attributes => { :code => "change_project_protection_level" }
+    assert_xml_tag tag: "status", attributes: { code: "change_project_protection_level" }
+    login_king
+    put url_for(controller: :source, action: :update_project_meta, project: "home:adrian:Project"),
+        '<project name="home:adrian:Project"> <title/> <description/> <access><disable/></access> </project>'
+    assert_response :success
     delete "/source/home:adrian:Project"
     assert_response :success
   end
diff --git a/src/api/test/functional/release_management_test.rb b/src/api/test/functional/release_management_test.rb
index 41843a4ea99f399d4d08ef97a0d8275feb417a85..741fc85d590c24a0b28dab16382913d0d8708d50 100644
--- a/src/api/test/functional/release_management_test.rb
+++ b/src/api/test/functional/release_management_test.rb
@@ -6,11 +6,10 @@ class ReleaseManagementTests < ActionDispatch::IntegrationTest
 
   def setup
     reset_auth
+    wait_for_scheduler_start
   end
 
   def test_move_entire_project
-    wait_for_scheduler_start
-
     login_tom
 
     # try as non-admin
@@ -152,4 +151,106 @@ class ReleaseManagementTests < ActionDispatch::IntegrationTest
     delete "/source/TEST:BaseDistro"
     assert_response :success
   end
+
+  def test_copy_project_withbinaries
+    login_king
+
+    run_scheduler('i586')
+    run_scheduler('x86_64')
+    inject_build_job( 'home:Iggy', 'TestPack', '10.2', 'i586')
+    inject_build_job( 'home:Iggy', 'TestPack', '10.2', 'x86_64')
+    run_scheduler('i586')
+    run_scheduler('x86_64')
+    run_publisher
+
+    # prerequisite: is our source project setup correctly?
+    get '/build/home:Iggy/10.2/i586/TestPack'
+    assert_xml_tag :tag => 'binarylist', :children => { :count => 4 }
+    get '/build/home:Iggy/10.2/x86_64/TestPack'
+    assert_xml_tag :tag => 'binarylist', :children => { :count => 5 }
+
+    # our source project is not building
+    get '/build/home:Iggy/_result'
+    assert_xml_tag :parent => { tag: 'result',
+                                :attributes => { project:    'home:Iggy',
+                                                 repository: '10.2',
+                                                 arch:       'i586',
+                                                 code:       'published',
+                                                 state:      'published' }
+      },
+      tag: 'status',
+      :attributes => { package: 'TestPack',
+                       code:    'succeeded' }
+    assert_xml_tag :parent => {
+        tag: 'result',
+        :attributes => { project:    'home:Iggy',
+                         repository: '10.2',
+                         arch:       'x86_64',
+                         code:       'published',
+                         state:      'published' } },
+      :tag => 'status', :attributes => { package: 'TestPack',
+                                         code:    'succeeded' }
+
+    # copy project with binaries
+    post '/source/IggyHomeCopy?cmd=copy&oproject=home:Iggy&noservice=1&withbinaries=1&nodelay=1'
+    assert_response :success
+
+    # let scheduler process events...
+    run_scheduler('i586')
+    run_scheduler('x86_64')
+    run_publisher
+
+    # get copy project meta and verify copied repositories
+    get '/source/IggyHomeCopy/_meta'
+    assert_response :success
+
+    assert_xml_tag :parent => { :tag => 'project', :attributes => { :name => 'IggyHomeCopy' } },
+      :tag => 'repository', :attributes => { :name => '10.2' }
+
+    assert_xml_tag :parent => { :tag => 'repository', :attributes => { :name => '10.2' } },
+      :tag => 'path', :attributes => { :project => 'BaseDistro', :repository => 'BaseDistro_repo' }
+
+    assert_xml_tag :parent => { :tag => 'repository', :attributes => { :name => '10.2' } },
+      :tag => 'arch', :content => 'i586'
+
+    assert_xml_tag :parent => { :tag => 'repository', :attributes => { :name => '10.2' } },
+      :tag => 'arch', :content => 'x86_64'
+
+    # check build results are copied correctly
+    get '/build/IggyHomeCopy/_result'
+    assert_xml_tag :parent => {
+                   tag: 'result',
+                   :attributes => { project:    'IggyHomeCopy',
+                                    repository: '10.2',
+                                    arch:       'i586',
+                                    code:       'published',
+                                    state:      'published' } },
+      tag: 'status',
+      :attributes => { package: 'TestPack', code: 'succeeded' }
+
+    assert_xml_tag :parent => {
+                                tag: 'result',
+                                :attributes => { project:    'IggyHomeCopy',
+                                                 repository: '10.2',
+                                                 arch:       'x86_64',
+                                                 code:       'published',
+                                                 state:      'published' }
+        },
+        tag: 'status',
+        :attributes => { package: 'TestPack', code: 'succeeded' }
+
+    # check that the same binaries are copied
+    get '/build/home:Iggy/10.2/i586/TestPack'
+    assert_xml_tag :tag => 'binarylist', :children => { :count => 4 }
+    get '/build/IggyHomeCopy/10.2/i586/TestPack'
+    assert_xml_tag :tag => 'binarylist', :children => { :count => 4 }
+    get '/build/home:Iggy/10.2/x86_64/TestPack'
+    assert_xml_tag :tag => 'binarylist', :children => { :count => 5 }
+    get '/build/IggyHomeCopy/10.2/x86_64/TestPack'
+    assert_xml_tag :tag => 'binarylist', :children => { :count => 5 }
+
+    # cleanup
+    delete '/source/IggyHomeCopy'
+    assert_response :success
+  end
 end
diff --git a/src/api/test/functional/request_controller_test.rb b/src/api/test/functional/request_controller_test.rb
index 84caf8c9de215c4d7908481409178fe0b8c8e838..d2629decd1f778504b8b70db72d73e670095d997 100644
--- a/src/api/test/functional/request_controller_test.rb
+++ b/src/api/test/functional/request_controller_test.rb
@@ -1,5 +1,6 @@
 # encoding: UTF-8
 # rubocop:disable Metrics/LineLength
+# rubocop:disable Metrics/ClassLength
 require File.expand_path(File.dirname(__FILE__) + '/..') + '/test_helper'
 require 'request_controller'
 
@@ -53,6 +54,15 @@ XML
     end
   end
 
+  def test_invalid_command
+    post '/request?cmd=INVALID'
+    assert_response 401
+    login_king
+    post '/request?cmd=INVALID'
+    assert_response 400
+    assert_xml_tag(tag: 'status', attributes: { code: 'unknown_command' })
+  end
+
   def test_get_requests_collection
     login_king
     get '/request', view: 'collection', reviewstates: 'accepted'
@@ -1526,10 +1536,10 @@ XML
     # id2 = node.value(:id)
 
     # delete projects
-    delete '/source/home:tom:branches:kde4'
-    assert_response :success
     delete '/source/home:tom:branches:home:tom:branches:kde4'
     assert_response :success
+    delete '/source/home:tom:branches:kde4'
+    assert_response :success
 
     # request got automatically revoked
     get "/request/#{id1}"
diff --git a/src/api/test/functional/source_controller_test.rb b/src/api/test/functional/source_controller_test.rb
index c5259b9c43886753a272e49afd90585571cf6747..45145aa7b0acb75e50c08d3c3b6ffa588e97981e 100644
--- a/src/api/test/functional/source_controller_test.rb
+++ b/src/api/test/functional/source_controller_test.rb
@@ -333,7 +333,7 @@ class SourceControllerTest < ActionDispatch::IntegrationTest
 
     # Change description
     xml = @response.body
-    new_desc = 'Changed description'
+    new_desc = 'Changed description 1'
     doc = ActiveXML::Node.new(xml)
     d = doc.find_first('description')
     d.text = new_desc
@@ -583,7 +583,7 @@ class SourceControllerTest < ActionDispatch::IntegrationTest
 
     # Change description
     xml = @response.body
-    new_desc = 'Changed description'
+    new_desc = 'Changed description 2'
     doc = REXML::Document.new(xml)
     d = doc.elements['//description']
     d.text = new_desc
@@ -848,13 +848,12 @@ class SourceControllerTest < ActionDispatch::IntegrationTest
   def test_put_package_meta_with_invalid_permissions
     login_tom
     # The user is valid, but has weak permissions
-
-    get url_for(:controller => :source, :action => :show_package_meta, :project => 'kde4', :package => 'kdelibs')
+    get url_for(controller: :source, action: :show_package_meta, project: 'kde4', package: 'kdelibs')
     assert_response :success
 
     # Change description
     xml = @response.body
-    new_desc = 'Changed description'
+    new_desc = 'Changed description 4'
     olddoc = REXML::Document.new(xml)
     doc = REXML::Document.new(xml)
     d = doc.elements['//description']
@@ -907,7 +906,7 @@ class SourceControllerTest < ActionDispatch::IntegrationTest
     end
     # Change description
     xml = @response.body
-    new_desc = 'Changed description'
+    new_desc = 'Changed description 3'
     doc = REXML::Document.new(xml)
     d = doc.elements['//description']
     d.text = new_desc
@@ -1786,6 +1785,18 @@ class SourceControllerTest < ActionDispatch::IntegrationTest
     assert_response :success
 
     # delete entire project
+    prepare_request_with_user 'fredlibs', 'buildservice'
+    get '/source/kde4/_meta'
+    assert_response :success
+    meta_before_delete = @response.body
+    # kind of stupid, but do_change_project_meta_test modifies backend data and does not clean up
+    put '/source/kde4/_meta', meta_before_delete
+    assert_response :success
+    get '/source/kde4/_project/_history?meta=1'
+    assert_response :success
+    node = ActiveXML::Node.new(@response.body)
+    revision = node.each(:revision).last.value :rev
+    expected_revision = revision.to_i + 1
     delete '/source/kde4?user=illegal&comment=drop%20project'
     assert_response :success
 
@@ -1816,9 +1827,13 @@ class SourceControllerTest < ActionDispatch::IntegrationTest
     assert_xml_tag(:parent => { :tag => 'revision' }, :tag => 'user', :content => 'fredlibs')
     assert_xml_tag(:parent => { :tag => 'revision' }, :tag => 'comment', :content => 'drop project')
     get '/source/kde4/_project/_history?meta=1&deleted=1'
-    assert_xml_tag(:parent => { :tag => 'revision' }, :tag => 'user', :content => 'fredlibs')
-    assert_xml_tag(:parent => { :tag => 'revision' }, :tag => 'comment', :content => 'drop project')
     assert_response :success
+    assert_xml_tag(parent: { tag: 'revision' }, tag: 'user', content: 'fredlibs')
+    assert_xml_tag(parent: { tag: 'revision' }, tag: 'comment', content: 'drop project')
+    # there must be only one change
+    node = ActiveXML::Node.new(@response.body)
+    revision = node.each(:revision).last.value :rev
+    assert_equal expected_revision, revision.to_i
 
     prepare_request_with_user 'fredlibs', 'buildservice'
     # undelete project
@@ -1833,10 +1848,10 @@ class SourceControllerTest < ActionDispatch::IntegrationTest
     get '/source/kde4'
     assert_response :success
     get '/source/kde4/_project'
-
     assert_response :success
     get '/source/kde4/_meta'
     assert_response :success
+    assert_equal meta_before_delete, @response.body
     get '/source/kde4/kdelibs'
     assert_response :success
     get '/source/kde4/kdelibs/_meta'
diff --git a/src/api/test/functional/source_services_test.rb b/src/api/test/functional/source_services_test.rb
index e2f1882a1999d355d53a059b56df736a4f972cb0..531d7dc53fdfd1f2dbb37e573386aae2e1fcc212 100644
--- a/src/api/test/functional/source_services_test.rb
+++ b/src/api/test/functional/source_services_test.rb
@@ -534,7 +534,24 @@ class SourceServicesTest < ActionDispatch::IntegrationTest
     assert_response 404
     assert_match(/no source service defined/, @response.body)
 
-    # and drop stuff as tom
+    # Locking user blocks the trigger
+    tom = User.find_by_login("tom")
+    tom.state = User::STATES['locked']
+    tom.save!
+    # with right token
+    post '/trigger/runservice', nil, { 'Authorization' => "Token #{token}" }
+    # success, but no source service configured :)
+    assert_response 403
+    assert_xml_tag tag: "status", attributes: { code: "no_permission" }
+    # with global token
+    post '/trigger/runservice?project=home:tom&package=service', nil, { 'Authorization' => "Token #{alltoken}" }
+    # success, but no source service configured :)
+    assert_response 403
+    assert_xml_tag tag: "status", attributes: { code: "no_permission" }
+
+    # reset and drop stuff as tom
+    tom.state = User::STATES['confirmed']
+    tom.save!
     login_tom
     get '/person/tom/token'
     assert_response :success
diff --git a/src/api/test/functional/webui/download_on_demand_controller_test.rb b/src/api/test/functional/webui/download_on_demand_controller_test.rb
index a709570cb010995880f179a2432a975b1c77c7f9..5c7f77393683fcdd53d5049f8031eae066423b18 100644
--- a/src/api/test/functional/webui/download_on_demand_controller_test.rb
+++ b/src/api/test/functional/webui/download_on_demand_controller_test.rb
@@ -46,109 +46,4 @@ class Webui::DownloadOnDemandControllerTest < Webui::IntegrationTest
     page.wont_have_link 'http://mola.org2'
     page.wont_have_text 'rpmmd'
   end
-
-  def test_adding_download_on_demand # spec/features/webui/projects_spec.rb
-    use_js
-
-    # Login as admin
-    login_king
-    visit(project_repositories_path(project: "home:user5"))
-    click_link("Add DoD repository")
-
-    # Fill in the form and send a working dod data
-    fill_in("Repository name", with: "My DoD repository")
-    select('i586', from: 'Architecture')
-    select('rpmmd', from: 'Type')
-    fill_in('Url', with: 'http://somerandomurl.es')
-    fill_in('Arch. Filter', with: 'i586, noarch')
-    fill_in('Master Url', with: 'http://somerandomurl2.es')
-    fill_in('SSL Fingerprint', with: '293470239742093')
-    fill_in('Public Key', with: 'JLKSDJFSJ83U4902RKLJSDFLJF2J9IJ23OJFKJFSDF')
-    click_button('Save')
-
-    within "#repository-list" do
-      find(:xpath, "//a[@href='/project/repository_state/home:user5/My%20DoD%20repository']").must_have_text("My DoD repository")
-      find_link('Add')
-      find(".edit-dod-repository-link-container").must_have_link('Edit')
-      find(".edit-dod-repository-link-container").must_have_link('Delete')
-      page.must_have_link 'http://somerandomurl.es'
-      page.must_have_text 'rpmmd'
-    end
-
-    click_link("Repositories")
-    click_link("Add")
-
-    # Fill in the form and send a not working dod data
-    select('x86_64', from: 'Architecture')
-    select('rpmmd', from: 'Type')
-    fill_in('Url', with: '')
-    click_button('Add Download on Demand')
-    find(:id, 'flash-messages').must_have_text("Download on Demand can't be created: Validation failed: Url can't be blank")
-  end
-
-  def test_editing_download_on_demand # spec/features/webui/projects_spec.rb
-    use_js
-
-    # Login as admin
-    login_king
-    visit(project_show_path(project: "home:user5"))
-
-    # Updating via meta
-    click_link("Advanced")
-    click_link("Meta")
-    page.evaluate_script("editors[0].setValue(\"#{PROJECT_WITH_DOWNLOAD_ON_DEMAND.gsub("\n", '\n')}\");")
-    click_button("Save")
-    find(:id, 'flash-messages').must_have_text('Config successfully saved!')
-
-    click_link("Repositories")
-    within(:css, "span.edit-dod-repository-link-container") do
-      click_link("Edit")
-    end
-
-    # Fill in the form and send a working dod data
-    select('i586', from: 'Architecture')
-    select('deb', from: 'Type')
-    fill_in('Url', with: 'http://somerandomurl_2.es')
-    fill_in('Arch. Filter', with: 'i586, noarch')
-    fill_in('Master Url', with: 'http://somerandomurl__2.es')
-    fill_in('SSL Fingerprint', with: '33333333444444')
-    fill_in('Public Key', with: '902RKLJSDFLJF902RKLJSDFLJF902RKLJSDFLJF')
-    click_button('Update Download on Demand')
-    find(:id, 'flash-messages').must_have_text('Successfully updated Download on Demand')
-    page.must_have_link 'http://somerandomurl_2.es'
-    page.must_have_text 'deb'
-
-    click_link("Repositories")
-    within(:css, "span.edit-dod-repository-link-container") do
-      click_link("Edit")
-    end
-
-    # Fill in the form and send a not working dod data
-    fill_in('Url', with: '')
-    click_button('Update Download on Demand')
-    find(:id, 'flash-messages').must_have_text("Download on Demand can't be updated: Validation failed: Url can't be blank")
-    page.must_have_link 'http://somerandomurl_2.es'
-  end
-
-  def test_destroying_download_on_demand # spec/features/webui/projects_spec.rb
-    use_js
-
-    # Login as admin
-    login_king
-    visit(project_show_path(project: "home:user5"))
-
-    # Updating via meta
-    click_link("Advanced")
-    click_link("Meta")
-    page.evaluate_script("editors[0].setValue(\"#{PROJECT_WITH_SEVERAL_DOWNLOAD_ON_DEMAND.gsub("\n", '\n')}\");")
-    click_button("Save")
-    find(:id, 'flash-messages').must_have_text('Config successfully saved!')
-
-    click_link("Repositories")
-    first(:xpath, "//a[text()='Delete']").click
-
-    page.wont_have_text 'Download on demand repositories'
-    page.wont_have_link 'http://mola.org2'
-    page.wont_have_text 'rpmmd'
-  end
 end
diff --git a/src/api/test/functional/webui/package_controller_test.rb b/src/api/test/functional/webui/package_controller_test.rb
index f13aebc99558d3150d46641a0de4b6673254c36f..1eddd1e3b01207dba7abd7efbfac43099c77d5fc 100644
--- a/src/api/test/functional/webui/package_controller_test.rb
+++ b/src/api/test/functional/webui/package_controller_test.rb
@@ -547,4 +547,61 @@ class Webui::PackageControllerTest < Webui::IntegrationTest
     page.all(:link, 'Trigger Rebuild')[0].click
     find('#flash-messages').must_have_text('Triggered rebuild for BaseDistro2.0/pack2.linked successfully.')
   end
+
+  def test_revert_to_revision
+    use_js
+    login_king
+    # create test package
+    visit project_show_path(project: "BaseDistro2.0")
+    click_link "Create package"
+    fill_in 'name', with: 'tst_pack'
+    click_button "Save changes"
+
+    # add 6 new revision to source package
+    6.times { |i| put '/source/BaseDistro2.0/tst_pack/rev_file_test', "revision #{(i + 1)}" }
+    # check latest revision
+    visit project_show_path(project: "BaseDistro2.0")
+    click_link "tst_pack"
+    click_link "rev_file_test"
+    page.must_have_text "revision 6"
+
+    # go to revision page and select second last revision (rev5)
+    visit package_view_revisions_path(project: 'BaseDistro2.0', package: 'tst_pack', meta: '0')
+    within('div#commit_item_5') do
+      click_link "Files changed"
+    end
+
+    # create "revert to revision" submit request
+    page.must_have_text "Changes of Revision 5"
+    click_link "Revert BaseDistro2.0 / tst_pack to revision 5"
+
+    # use same project, but set different target package name
+    page.must_have_text "Create Submit Request"
+    fill_in 'targetproject', with: 'BaseDistro2.0'
+    fill_in 'targetpackage', with: 'tst_pack_rev5'
+    fill_in 'description', with: 'testing revert to revision 5'
+    click_button 'Ok'
+
+    # check that request was submitted
+    page.wont_have_selector '.dialog' # wait for the reload
+    requestid = flash_message.gsub(%r{Created submit request (\d*) to BaseDistro2.0}, '\1').to_i
+
+    # open request from project page and accept it
+    visit project_show_path(project: "BaseDistro2.0")
+    click_link "open request"
+    find("a[href='/request/show/#{requestid}']").click
+    page.must_have_text "testing revert to revision 5"
+    page.must_have_text "Submit package BaseDistro2.0 / tst_pack (revision 5) to package BaseDistro2.0 / tst_pack_rev5"
+    click_button "Accept request"
+
+    # go to reverted package
+    visit project_show_path(project: "BaseDistro2.0")
+    click_link "tst_pack_rev5"
+    page.must_have_text "testing revert to revision 5"
+
+    # verify that correct revision was reverted
+    click_link "rev_file_test"
+    page.wont_have_text "revision 6" # from the latest revision
+    page.must_have_text "revision 5" # from the reverted revision
+  end
 end
diff --git a/src/api/test/unit/code_quality_test.rb b/src/api/test/unit/code_quality_test.rb
index 3e0a62f18fc38de69c6a6d84cadecb94208599bf..25d27ca962afec6179037ebc93844d544869798e 100644
--- a/src/api/test/unit/code_quality_test.rb
+++ b/src/api/test/unit/code_quality_test.rb
@@ -74,7 +74,7 @@ class CodeQualityTest < ActiveSupport::TestCase
       'BsRequestAction#create_expand_package'                                   => 443.16,
       'BsRequestAction#default_reviewers'                                       => 141.02,
       'BsRequestAction#store_from_xml'                                          => 88.01,
-      'BsRequestActionMaintenanceIncident#_merge_pkg_into_maintenance_incident' => 130.81,
+      'BsRequestActionMaintenanceIncident#_merge_pkg_into_maintenance_incident' => 153.15,
       'BsRequestActionMaintenanceRelease#sanity_check!'                         => 81.82,
       'BsRequestActionSubmit#execute_accept'                                    => 126.42,
       'BsRequestPermissionCheck#cmd_changestate_permissions'                    => 117.09,
@@ -98,9 +98,11 @@ class CodeQualityTest < ActiveSupport::TestCase
       'User::find_with_credentials'                                             => 131.43,
       'UserLdapStrategy::render_grouplist_ldap'                                 => 100.3,
       'Webui::DriverUpdateController#save'                                      => 91.69,
-      'Webui::PackageController#submit_request'                                 => 95.89,
+      'Webui::PackageController#submit_request'                                 => 101.98,
       'Webui::PatchinfoController#save'                                         => 240.1,
       'Webui::ProjectController#check_devel_package_status'                     => 81.95,
+      'Webui::ProjectController#save_meta'                                      => 83.61,
+      'Webui::RequestController#show'                                           => 91.96,
       'Webui::SearchController#set_parameters'                                  => 98.04,
       'WizardController#package_wizard'                                         => 97.46
   }
diff --git a/src/api/test/unit/user_test.rb b/src/api/test/unit/user_test.rb
index cf4a69d78c17dd7ec1b0d9798bf9dff413fb715a..c5ea03af19f8b96497ad3751fe4b43b7f08c0b42 100644
--- a/src/api/test/unit/user_test.rb
+++ b/src/api/test/unit/user_test.rb
@@ -8,24 +8,6 @@ class UserTest < ActiveSupport::TestCase
     @user = User.find_by_login('Iggy')
   end
 
-  def test_login
-    user = User.authenticate("tom", "buildservice")
-    assert_equal User.find_by(login: "tom"), user
-    assert_equal User.find_by(login: "tom"), User.current
-
-    user = User.authenticate("tom", "wrong_pw")
-    assert_equal nil, user
-    assert_equal nil, User.current
-
-    user = User.authenticate("nonexistant", "foobar")
-    assert_equal nil, user
-    assert_equal nil, User.current
-
-    user = User.authenticate("unconfirmed_user", "thunder")
-    assert_equal nil, user, "Should not authenticate users with state 'unconfirmed'"
-    assert_equal nil, User.current
-  end
-
   def test_create_home_project # spec/models/user_spec.rb
     User.create(login: 'moises', email: 'moises@home.com', password: '123456')
     assert Project.find_by(name: 'home:moises')
diff --git a/src/backend/BSHTTP.pm b/src/backend/BSHTTP.pm
index 54fb0b3b629dd9115aa4120c2ac6ac5b7f7928b7..17bd8be5b1603785861ad0f4440a07320921741f 100644
--- a/src/backend/BSHTTP.pm
+++ b/src/backend/BSHTTP.pm
@@ -376,6 +376,7 @@ sub cpio_sender {
       my $r = 0;
       while(1) {
 	$r = sysread(F, $data, $l > 8192 ? 8192 : $l, length($data)) if $l;
+	die "Error while reading file $file->{filename}: $!\n" if ! defined($r);
 	$data .= $pad if $r == $l;
 	swrite($sock, $data, $param->{'chunked'});
 	$data = '';
diff --git a/src/backend/BSSched/BuildJob/KiwiImage.pm b/src/backend/BSSched/BuildJob/KiwiImage.pm
index 3f9352685e1744b2930d203d802a9cd9333aa17e..3f4fb3bb843572f8fcfff61bd896a15637d5c400 100644
--- a/src/backend/BSSched/BuildJob/KiwiImage.pm
+++ b/src/backend/BSSched/BuildJob/KiwiImage.pm
@@ -230,7 +230,7 @@ sub build {
     no warnings 'redefine';
     local *Build::expand = sub { $_[0] = $xp; goto &BSSolv::expander::expand; };
     use warnings 'redefine';
-    return BSSched::BuildJob::create({ %$ctx, 'conf' => $bconf, 'prpsearchpath' => [], 'pool' => $pool }, $packid, $pdata, $info, [], $edeps, $reason, 0);
+    return BSSched::BuildJob::create({ %$ctx, 'conf' => $bconf, 'prpsearchpath' => [], 'pool' => $pool, 'realctx' => $ctx }, $packid, $pdata, $info, [], $edeps, $reason, 0);
   } else {
     # repo has a configured path, expand kiwi system with it
     my $prp = "$projid/$repoid";
diff --git a/src/backend/BSSched/BuildJob/Upload.pm b/src/backend/BSSched/BuildJob/Upload.pm
index b4b1e00b9f5dda88ced2b02777312f7cfe11efc9..8baac068a8b396b71e01345947f9d855facac873 100644
--- a/src/backend/BSSched/BuildJob/Upload.pm
+++ b/src/backend/BSSched/BuildJob/Upload.pm
@@ -69,8 +69,10 @@ sub jobfinished {
   my $gdst = "$gctx->{'reporoot'}/$prp/$myarch";
   my $dst = "$gdst/$packid";
   mkdir_p($dst);
+  # find the meta for the successful build
   my $meta;
-  $meta = "$jobdatadir/meta" if -e "$jobdatadir/meta";
+  $meta = "$jobdatadir/.meta.success" if -e "$jobdatadir/.meta.success";
+  $meta = "$jobdatadir/meta" if !$meta && -e "$jobdatadir/meta";
   print "  - $prp: $packid uploaded\n";
   my $useforbuildenabled = 1;
   $useforbuildenabled = BSUtil::enabled($repoid, $projpacks->{$projid}->{'useforbuild'}, $useforbuildenabled, $myarch);
@@ -78,10 +80,19 @@ sub jobfinished {
   my $prpsearchpath = $gctx->{'prpsearchpath'}->{$prp};
   BSSched::BuildResult::update_dst_full($gctx, $prp, $packid, $jobdatadir, $meta, $useforbuildenabled, $prpsearchpath);
   $changed->{$prp} = 2 if $useforbuildenabled;
-  if ($meta) {
+  if (-e "$jobdatadir/.logfile.success") {
+    mkdir_p("$gdst/:logfiles.success");
+    rename("$jobdatadir/.logfile.success", "$gdst/:logfiles.success/$packid");
+  }
+  if (-e "$jobdatadir/.logfile.fail") {
+    mkdir_p("$gdst/:logfiles.fail");
+    rename("$jobdatadir/.logfile.fail", "$gdst/:logfiles.fail/$packid");
+  }
+  if (-e "$jobdatadir/meta") {
     mkdir_p("$gdst/:meta");
-    rename($meta, "$gdst/:meta/$packid");
+    rename("$jobdatadir/meta", "$gdst/:meta/$packid");
   }
+  rename("$jobdatadir/logfile", "$dst/logfile") if -e "$jobdatadir/logfile";
   my $repounchanged = $gctx->{'repounchanged'};
   delete $repounchanged->{$prp} if $useforbuildenabled;
   $repounchanged->{$prp} = 2 if $repounchanged->{$prp};
diff --git a/src/backend/BSSched/BuildResult.pm b/src/backend/BSSched/BuildResult.pm
index 80aaf0a27553555cdca8c99a959477e21420e9e0..b2cafc20e9a1cef5966f4a09e22571c3963e53b9 100644
--- a/src/backend/BSSched/BuildResult.pm
+++ b/src/backend/BSSched/BuildResult.pm
@@ -324,7 +324,7 @@ sub update_dst_full {
   my $jobbininfo;
   if (defined($jobdir)) {
     @jobfiles = sort(ls($jobdir));
-    @jobfiles = grep {$_ ne 'history' && $_ ne 'logfile' && $_ ne 'meta' && $_ ne 'status' && $_ ne 'reason' && $_ ne '.bininfo'} @jobfiles;
+    @jobfiles = grep {$_ ne 'history' && $_ ne 'logfile' && $_ ne 'meta' && $_ ne 'status' && $_ ne 'reason' && $_ ne '.bininfo' && $_ ne '.meta.success' && $_ ne '.logfile.success' && $_ ne '.logfile.fail'} @jobfiles;
     $jobbininfo = BSUtil::retrieve("$jobdir/.bininfo", 1);
     if ($jobbininfo && !($jobdir eq $dst) && !$jobbininfo->{'.bininfo'}) {
       # old style jobdir bininfo, ignore
diff --git a/src/backend/BSSched/DoD.pm b/src/backend/BSSched/DoD.pm
index 1ac4d1860a76017514852c465dc3523a8de736f0..7130de5050b9ccdd336a0fe7d9515cb90065e0b6 100644
--- a/src/backend/BSSched/DoD.pm
+++ b/src/backend/BSSched/DoD.pm
@@ -128,6 +128,7 @@ sub clean_obsolete_dodpackages {
 
 sub dodcheck {
   my ($ctx, $pool, $arch, @pkgs) = @_;
+  $ctx = $ctx->{'realctx'} if $ctx->{'realctx'};	# we need the real one to add entries
   my %names;
   if (defined &BSSolv::repo::dodcookie) {
     %names = (%names, $_->pkgnames()) for grep {$_->dodurl() || $_->dodcookie()} $pool->repos();
diff --git a/src/backend/BSXML.pm b/src/backend/BSXML.pm
index 958ba52ddf4d8675b7a228917d140b63cdde9b59..f9d296b500dced101748366b7a1b64215bd1acde 100644
--- a/src/backend/BSXML.pm
+++ b/src/backend/BSXML.pm
@@ -140,6 +140,7 @@ our $proj = [
 	 [],
         'title',
         'description',
+        'url',
      [[	'link' =>
 	    'project',
      ]],
@@ -730,6 +731,7 @@ our $worker = [
           [ 'flag' ],
         ],
         'processors',
+        'jobs',         # compat for OBS 2.8 worker
 	'memory',	# in MBytes
 	'swap',		# in MBytes
 	'disk',		# in MBytes
diff --git a/src/backend/bs_publish b/src/backend/bs_publish
index 65bb56a01f953650d97616cfe73357de200e8ce6..77a06f1e31cf8db7177d0fd4163a01777fb46d11 100755
--- a/src/backend/bs_publish
+++ b/src/backend/bs_publish
@@ -808,7 +808,7 @@ sub createrepo_staticlinks {
         # kiwi appliance
         $link = "$1$3";
         $link = "$1-$2$3" if $versioned;
-      } elsif (/^(.*)-(\d+\.\d+\.\d+)?(\.\w+)?-Build\d+\.\d+(\.(raw.install.raw.xz|raw.xz|box|json|install.iso|tbz|tgz|vmx|vmdk|vhdfixed.xz|iso|qcow2|qcow2.xz|ova)?(?:\.sha256)?)$/s) {
+      } elsif (/^(.*)-(\d+\.\d+\.\d+)?(\.\w+)?-Build\d+\.\d+(\.(raw.install.raw.xz|raw.xz|box|json|install.iso|tbz|tgz|vmx|vmdk|vdi|vhdfixed.xz|iso|qcow2|qcow2.xz|ova)?(?:\.sha256)?)$/s) {
         # kiwi appliance
         my $profile = $3 || "";
         $link = "$1$profile$4";
@@ -1477,7 +1477,7 @@ sub publish {
 	  $p = "$bin";
 	} elsif ($bin =~ /\.dsc(?:\.sha256)?$/) {
 	  $p = "$bin";
-	} elsif ($bin =~ /\.(?:box|json|ovf|qcow2|qcow2\.xz|vhdfixed.xz|vmx|vmdk)(?:\.sha256)?$/) {
+	} elsif ($bin =~ /\.(?:box|json|ovf|qcow2|qcow2\.xz|vdi|vhdfixed.xz|vmx|vmdk)(?:\.sha256)?$/) {
 	  $p = "$bin";
         } elsif ($bin =~ /^(.*)\.packages$/) {
 	  $p = "$bin";
@@ -2111,6 +2111,23 @@ publishprog_done:
     }
   }
 
+  # support for regex usage in $BSConfig::unpublishedhook
+  my $unpublish_prp = $prp;
+  if ($BSConfig::unpublishedhook_use_regex || $BSConfig::unpublishedhook_use_regex) {
+    for my $key (sort {$b cmp $a} keys %{$BSConfig::unpublishedhook}) {
+      if ($prp =~ /^$key/) {
+        $unpublish_prp = $key;
+        last;
+      }
+    }
+  }
+  if ($BSConfig::unpublishedhook && $BSConfig::unpublishedhook->{$unpublish_prp}) {
+    my $hook = $BSConfig::unpublishedhook->{$unpublish_prp};
+    $hook = [ $hook ] unless ref $hook;
+    print "    calling unpublished hook @$hook\n";
+    qsystem(@$hook, $prp, $extrep, @db_deleted) && warn("    @$hook failed: $?\n");
+  }
+
   # support for regex usage in $BSConfig::publishedhook
   my $publish_prp = $prp;
   if ($BSConfig::publishedhook_use_regex || $BSConfig::publishedhook_use_regex) {
diff --git a/src/backend/bs_repserver b/src/backend/bs_repserver
index 6c52647a976fd92f1ffef8d08fa2b5c1e4ea38ea..e8de797b219502cd30aed4ec3c9f98ba20945405 100755
--- a/src/backend/bs_repserver
+++ b/src/backend/bs_repserver
@@ -2057,7 +2057,8 @@ sub copybuild {
     die("job lock failed\n");
   }
   my $dir = "$jobsdir/$arch/$job:dir";
-  my $odir = "$reporoot/$oprojid/$orepoid/$arch/$opackid";
+  my $ogdst = "$reporoot/$oprojid/$orepoid/$arch";
+  my $odir = "$ogdst/$opackid";
   mkdir_p($dir);
   my %delayed_linking;
   my $needsign;
@@ -2112,7 +2113,11 @@ sub copybuild {
       }
     }
   }
-  link("$odir/.meta.success", "$dir/meta") if -e "$odir/.meta.success";
+  link("$odir/.meta.success", "$dir/.meta.success") if -e "$odir/.meta.success";
+  link("$ogdst/:meta/$opackid", "$dir/meta") if -e "$ogdst/:meta/$opackid";
+  link("$ogdst/:logfiles.success/$opackid", "$dir/.logfile.success");
+  link("$ogdst/:logfiles.fail/$opackid", "$dir/.logfile.fail");
+  BSUtil::touch("$dir/.preinstallimage") if -e "$odir/.preinstallimage";
 
   # we run the linking of directory trees in background, since it can take a long time
   # for simple files it happened already