Просмотр исходного кода

Merge pull request #24 from sharelatex/bg-support-project-version

support project version
Brian Gough 8 лет назад
Родитель
Сommit
dffc0a42c3

+ 3 - 3
services/document-updater/app/coffee/HttpController.coffee

@@ -161,10 +161,10 @@ module.exports = HttpController =
 	updateProject: (req, res, next = (error) ->) ->
 		timer = new Metrics.Timer("http.updateProject")
 		project_id = req.params.project_id
-		{userId, docUpdates, fileUpdates} = req.body
-		logger.log {project_id, docUpdates, fileUpdates}, "updating project via http"
+		{userId, docUpdates, fileUpdates, version} = req.body
+		logger.log {project_id, docUpdates, fileUpdates, version}, "updating project via http"
 
-		ProjectManager.updateProjectWithLocks project_id, userId, docUpdates, fileUpdates, (error) ->
+		ProjectManager.updateProjectWithLocks project_id, userId, docUpdates, fileUpdates, version, (error) ->
 			timer.done()
 			return next(error) if error?
 			logger.log project_id: project_id, "updated project via http"

+ 21 - 19
services/document-updater/app/coffee/ProjectHistoryRedisManager.coffee

@@ -7,47 +7,49 @@ module.exports = ProjectHistoryRedisManager =
 	queueOps: (project_id, ops..., callback) ->
 		rclient.rpush projectHistoryKeys.projectHistoryOps({project_id}), ops..., callback
 
-	queueRenameEntity: (project_id, entity_type, entity_id, user_id, update, callback) ->
-		update =
-			pathname: update.pathname
-			new_pathname: update.newPathname
+	queueRenameEntity: (project_id, entity_type, entity_id, user_id, projectUpdate, callback) ->
+		projectUpdate =
+			pathname: projectUpdate.pathname
+			new_pathname: projectUpdate.newPathname
 			meta:
 				user_id: user_id
 				ts: new Date()
-		update[entity_type] = entity_id
+			version: projectUpdate.version
+		projectUpdate[entity_type] = entity_id
 
-		logger.log {project_id, update}, "queue rename operation to project-history"
-		jsonUpdate = JSON.stringify(update)
+		logger.log {project_id, projectUpdate}, "queue rename operation to project-history"
+		jsonUpdate = JSON.stringify(projectUpdate)
 
 		ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback
 
-	queueAddEntity: (project_id, entity_type, entitiy_id, user_id, update, callback = (error) ->) ->
-		update =
-			pathname: update.pathname
-			docLines: update.docLines
-			url: update.url
+	queueAddEntity: (project_id, entity_type, entitiy_id, user_id, projectUpdate, callback = (error) ->) ->
+		projectUpdate =
+			pathname: projectUpdate.pathname
+			docLines: projectUpdate.docLines
+			url: projectUpdate.url
 			meta:
 				user_id: user_id
 				ts: new Date()
-		update[entity_type] = entitiy_id
+			version: projectUpdate.version
+		projectUpdate[entity_type] = entitiy_id
 
-		logger.log {project_id, update}, "queue add operation to project-history"
-		jsonUpdate = JSON.stringify(update)
+		logger.log {project_id, projectUpdate}, "queue add operation to project-history"
+		jsonUpdate = JSON.stringify(projectUpdate)
 
 		ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback
 
 	queueResyncProjectStructure: (project_id, docs, files, callback) ->
 		logger.log {project_id, docs, files}, "queue project structure resync"
-		update =
+		projectUpdate =
 			resyncProjectStructure: { docs, files }
 			meta:
 				ts: new Date()
-		jsonUpdate = JSON.stringify update
+		jsonUpdate = JSON.stringify projectUpdate
 		ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback
 
 	queueResyncDocContent: (project_id, doc_id, lines, version, pathname, callback) ->
 		logger.log {project_id, doc_id, lines, version, pathname}, "queue doc content resync"
-		update =
+		projectUpdate =
 			resyncDocContent:
 				content: lines.join("\n"),
 				version: version
@@ -55,5 +57,5 @@ module.exports = ProjectHistoryRedisManager =
 			doc: doc_id
 			meta:
 				ts: new Date()
-		jsonUpdate = JSON.stringify update
+		jsonUpdate = JSON.stringify projectUpdate
 		ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback

+ 18 - 13
services/document-updater/app/coffee/ProjectManager.coffee

@@ -105,39 +105,44 @@ module.exports = ProjectManager =
 	clearProjectState: (project_id, callback = (error) ->) ->
 		RedisManager.clearProjectState project_id, callback
 
-	updateProjectWithLocks: (project_id, user_id, docUpdates, fileUpdates, _callback = (error) ->) ->
+	updateProjectWithLocks: (project_id, user_id, docUpdates, fileUpdates, version, _callback = (error) ->) ->
 		timer = new Metrics.Timer("projectManager.updateProject")
 		callback = (args...) ->
 			timer.done()
 			_callback(args...)
 
+		project_version = version 
+		project_subversion = 0 		# project versions can have multiple operations
+
 		project_ops_length = 0
 
-		handleDocUpdate = (update, cb) ->
-			doc_id = update.id
-			if update.docLines?
-				ProjectHistoryRedisManager.queueAddEntity project_id, 'doc', doc_id, user_id, update, (error, count) ->
+		handleDocUpdate = (projectUpdate, cb) ->
+			doc_id = projectUpdate.id
+			projectUpdate.version = "#{project_version}.#{project_subversion++}"
+			if projectUpdate.docLines?
+				ProjectHistoryRedisManager.queueAddEntity project_id, 'doc', doc_id, user_id, projectUpdate, (error, count) ->
 					project_ops_length = count
 					cb(error)
 			else
-				DocumentManager.renameDocWithLock project_id, doc_id, user_id, update, (error, count) ->
+				DocumentManager.renameDocWithLock project_id, doc_id, user_id, projectUpdate, (error, count) ->
 					project_ops_length = count
 					cb(error)
 
-		handleFileUpdate = (update, cb) ->
-			file_id = update.id
-			if update.url?
-				ProjectHistoryRedisManager.queueAddEntity project_id, 'file', file_id, user_id, update, (error, count) ->
+		handleFileUpdate = (projectUpdate, cb) ->
+			file_id = projectUpdate.id
+			projectUpdate.version = "#{project_version}.#{project_subversion++}"
+			if projectUpdate.url?
+				ProjectHistoryRedisManager.queueAddEntity project_id, 'file', file_id, user_id, projectUpdate, (error, count) ->
 					project_ops_length = count
 					cb(error)
 			else
-				ProjectHistoryRedisManager.queueRenameEntity project_id, 'file', file_id, user_id, update, (error, count) ->
+				ProjectHistoryRedisManager.queueRenameEntity project_id, 'file', file_id, user_id, projectUpdate, (error, count) ->
 					project_ops_length = count
 					cb(error)
 
-		async.each docUpdates, handleDocUpdate, (error) ->
+		async.eachSeries docUpdates, handleDocUpdate, (error) ->
 			return callback(error) if error?
-			async.each fileUpdates, handleFileUpdate, (error) ->
+			async.eachSeries fileUpdates, handleFileUpdate, (error) ->
 				return callback(error) if error?
 				if HistoryManager.shouldFlushHistoryOps(project_ops_length, docUpdates.length + fileUpdates.length, HistoryManager.FLUSH_PROJECT_EVERY_N_OPS)
 					HistoryManager.flushProjectChangesAsync project_id

+ 86 - 10
services/document-updater/test/acceptance/coffee/ApplyingUpdatesToProjectStructureTests.coffee

@@ -13,6 +13,7 @@ DocUpdaterApp = require "./helpers/DocUpdaterApp"
 describe "Applying updates to a project's structure", ->
 	before ->
 		@user_id = 'user-id-123'
+		@version = 1234
 
 	describe "renaming a file", ->
 		before (done) ->
@@ -24,7 +25,7 @@ describe "Applying updates to a project's structure", ->
 			@fileUpdates = [ @fileUpdate ]
 			DocUpdaterApp.ensureRunning (error) =>
 				throw error if error?
-				DocUpdaterClient.sendProjectUpdate @project_id, @user_id, [], @fileUpdates, (error) ->
+				DocUpdaterClient.sendProjectUpdate @project_id, @user_id, [], @fileUpdates, @version, (error) ->
 					throw error if error?
 					setTimeout done, 200
 
@@ -38,6 +39,7 @@ describe "Applying updates to a project's structure", ->
 				update.new_pathname.should.equal '/new-file-path'
 				update.meta.user_id.should.equal @user_id
 				update.meta.ts.should.be.a('string')
+				update.version.should.equal "#{@version}.0"
 
 				done()
 
@@ -52,7 +54,7 @@ describe "Applying updates to a project's structure", ->
 		describe "when the document is not loaded", ->
 			before (done) ->
 				@project_id = DocUpdaterClient.randomId()
-				DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, [], (error) ->
+				DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, [], @version, (error) ->
 					throw error if error?
 					setTimeout done, 200
 
@@ -66,6 +68,7 @@ describe "Applying updates to a project's structure", ->
 					update.new_pathname.should.equal '/new-doc-path'
 					update.meta.user_id.should.equal @user_id
 					update.meta.ts.should.be.a('string')
+					update.version.should.equal "#{@version}.0"
 
 					done()
 
@@ -76,7 +79,7 @@ describe "Applying updates to a project's structure", ->
 				DocUpdaterClient.preloadDoc @project_id, @docUpdate.id, (error) =>
 					throw error if error?
 					sinon.spy MockWebApi, "getDocument"
-					DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, [], (error) ->
+					DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, [], @version, (error) ->
 						throw error if error?
 						setTimeout done, 200
 
@@ -98,9 +101,77 @@ describe "Applying updates to a project's structure", ->
 					update.new_pathname.should.equal '/new-doc-path'
 					update.meta.user_id.should.equal @user_id
 					update.meta.ts.should.be.a('string')
+					update.version.should.equal "#{@version}.0"
 
 					done()
 
+	describe "renaming multiple documents and files", ->
+		before ->
+			@docUpdate0 =
+				id: DocUpdaterClient.randomId()
+				pathname: '/doc-path0'
+				newPathname: '/new-doc-path0'
+			@docUpdate1 =
+				id: DocUpdaterClient.randomId()
+				pathname: '/doc-path1'
+				newPathname: '/new-doc-path1'
+			@docUpdates = [ @docUpdate0, @docUpdate1 ]
+			@fileUpdate0 =
+				id: DocUpdaterClient.randomId()
+				pathname: '/file-path0'
+				newPathname: '/new-file-path0'
+			@fileUpdate1 =
+				id: DocUpdaterClient.randomId()
+				pathname: '/file-path1'
+				newPathname: '/new-file-path1'
+			@fileUpdates = [ @fileUpdate0, @fileUpdate1 ]
+
+		describe "when the documents are not loaded", ->
+			before (done) ->
+				@project_id = DocUpdaterClient.randomId()
+				DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, @fileUpdates, @version, (error) ->
+					throw error if error?
+					setTimeout done, 200
+
+			it "should push the applied doc renames to the project history api", (done) ->
+				rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
+					throw error if error?
+
+					update = JSON.parse(updates[0])
+					update.doc.should.equal @docUpdate0.id
+					update.pathname.should.equal '/doc-path0'
+					update.new_pathname.should.equal '/new-doc-path0'
+					update.meta.user_id.should.equal @user_id
+					update.meta.ts.should.be.a('string')
+					update.version.should.equal "#{@version}.0"
+
+					update = JSON.parse(updates[1])
+					update.doc.should.equal @docUpdate1.id
+					update.pathname.should.equal '/doc-path1'
+					update.new_pathname.should.equal '/new-doc-path1'
+					update.meta.user_id.should.equal @user_id
+					update.meta.ts.should.be.a('string')
+					update.version.should.equal "#{@version}.1"
+
+					update = JSON.parse(updates[2])
+					update.file.should.equal @fileUpdate0.id
+					update.pathname.should.equal '/file-path0'
+					update.new_pathname.should.equal '/new-file-path0'
+					update.meta.user_id.should.equal @user_id
+					update.meta.ts.should.be.a('string')
+					update.version.should.equal "#{@version}.2"
+
+					update = JSON.parse(updates[3])
+					update.file.should.equal @fileUpdate1.id
+					update.pathname.should.equal '/file-path1'
+					update.new_pathname.should.equal '/new-file-path1'
+					update.meta.user_id.should.equal @user_id
+					update.meta.ts.should.be.a('string')
+					update.version.should.equal "#{@version}.3"
+
+					done()
+
+
 	describe "adding a file", ->
 		before (done) ->
 			@project_id = DocUpdaterClient.randomId()
@@ -109,7 +180,7 @@ describe "Applying updates to a project's structure", ->
 				pathname: '/file-path'
 				url: 'filestore.example.com'
 			@fileUpdates = [ @fileUpdate ]
-			DocUpdaterClient.sendProjectUpdate @project_id, @user_id, [], @fileUpdates, (error) ->
+			DocUpdaterClient.sendProjectUpdate @project_id, @user_id, [], @fileUpdates, @version, (error) ->
 				throw error if error?
 				setTimeout done, 200
 
@@ -123,6 +194,7 @@ describe "Applying updates to a project's structure", ->
 				update.url.should.equal 'filestore.example.com'
 				update.meta.user_id.should.equal @user_id
 				update.meta.ts.should.be.a('string')
+				update.version.should.equal "#{@version}.0"
 
 				done()
 
@@ -134,7 +206,7 @@ describe "Applying updates to a project's structure", ->
 				pathname: '/file-path'
 				docLines: 'a\nb'
 			@docUpdates = [ @docUpdate ]
-			DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, [], (error) ->
+			DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, [], @version, (error) ->
 				throw error if error?
 				setTimeout done, 200
 
@@ -148,6 +220,7 @@ describe "Applying updates to a project's structure", ->
 				update.docLines.should.equal 'a\nb'
 				update.meta.user_id.should.equal @user_id
 				update.meta.ts.should.be.a('string')
+				update.version.should.equal "#{@version}.0"
 
 				done()
 
@@ -155,7 +228,8 @@ describe "Applying updates to a project's structure", ->
 		before (done) ->
 			@project_id = DocUpdaterClient.randomId()
 			@user_id = DocUpdaterClient.randomId()
-
+			@version0 = 12345
+			@version1 = @version0 + 1
 			updates = []
 			for v in [0..599] # Should flush after 500 ops
 				updates.push
@@ -168,9 +242,9 @@ describe "Applying updates to a project's structure", ->
 			# Send updates in chunks to causes multiple flushes
 			projectId = @project_id
 			userId = @project_id
-			DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(0, 250), [], (error) ->
+			DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(0, 250), [], @version0, (error) ->
 				throw error if error?
-				DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(250), [], (error) ->
+				DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(250), [], @version1, (error) ->
 					throw error if error?
 					setTimeout done, 2000
 
@@ -184,6 +258,8 @@ describe "Applying updates to a project's structure", ->
 		before (done) ->
 			@project_id = DocUpdaterClient.randomId()
 			@user_id = DocUpdaterClient.randomId()
+			@version0 = 12345
+			@version1 = @version0 + 1
 
 			updates = []
 			for v in [0..42] # Should flush after 500 ops
@@ -197,9 +273,9 @@ describe "Applying updates to a project's structure", ->
 			# Send updates in chunks
 			projectId = @project_id
 			userId = @project_id
-			DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(0, 10), [], (error) ->
+			DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(0, 10), [], @version0, (error) ->
 				throw error if error?
-				DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(10), [], (error) ->
+				DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(10), [], @version1, (error) ->
 					throw error if error?
 					setTimeout done, 2000
 

+ 2 - 2
services/document-updater/test/acceptance/coffee/helpers/DocUpdaterClient.coffee

@@ -87,9 +87,9 @@ module.exports = DocUpdaterClient =
 				body = JSON.parse(body)
 			callback error, res, body
 
-	sendProjectUpdate: (project_id, userId, docUpdates, fileUpdates, callback = (error) ->) ->
+	sendProjectUpdate: (project_id, userId, docUpdates, fileUpdates, version, callback = (error) ->) ->
 		request.post {
 			url: "http://localhost:3003/project/#{project_id}"
-			json: { userId, docUpdates, fileUpdates }
+			json: { userId, docUpdates, fileUpdates, version }
 		}, (error, res, body) ->
 			callback error, res, body

+ 5 - 4
services/document-updater/test/unit/coffee/HttpController/HttpControllerTests.coffee

@@ -512,19 +512,20 @@ describe "HttpController", ->
 			@userId = "user-id-123"
 			@docUpdates = sinon.stub()
 			@fileUpdates = sinon.stub()
+			@version = 1234567
 			@req =
-				body: {@userId, @docUpdates, @fileUpdates}
+				body: {@userId, @docUpdates, @fileUpdates, @version}
 				params:
 					project_id: @project_id
 
 		describe "successfully", ->
 			beforeEach ->
-				@ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(4)
+				@ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(5)
 				@HttpController.updateProject(@req, @res, @next)
 
 			it "should accept the change", ->
 				@ProjectManager.updateProjectWithLocks
-					.calledWith(@project_id, @userId, @docUpdates, @fileUpdates)
+					.calledWith(@project_id, @userId, @docUpdates, @fileUpdates, @version)
 					.should.equal true
 
 			it "should return a successful No Content response", ->
@@ -537,7 +538,7 @@ describe "HttpController", ->
 
 		describe "when an errors occurs", ->
 			beforeEach ->
-				@ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(4, new Error("oops"))
+				@ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(5, new Error("oops"))
 				@HttpController.updateProject(@req, @res, @next)
 
 			it "should call next with the error", ->

+ 34 - 19
services/document-updater/test/unit/coffee/ProjectManager/updateProjectTests.coffee

@@ -3,6 +3,7 @@ chai = require('chai')
 should = chai.should()
 modulePath = "../../../../app/js/ProjectManager.js"
 SandboxedModule = require('sandboxed-module')
+_ = require('underscore')
 
 describe "ProjectManager", ->
 	beforeEach ->
@@ -18,6 +19,7 @@ describe "ProjectManager", ->
 
 		@project_id = "project-id-123"
 		@user_id = "user-id-123"
+		@version = 1234567
 		@HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(false)
 		@HistoryManager.flushProjectChangesAsync = sinon.stub()
 		@callback = sinon.stub()
@@ -45,19 +47,22 @@ describe "ProjectManager", ->
 
 			describe "successfully", ->
 				beforeEach ->
-					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @callback
+					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
 
 				it "should rename the docs in the updates", ->
+					firstDocUpdateWithVersion = _.extend({}, @firstDocUpdate, {version: "#{@version}.0"})
+					secondDocUpdateWithVersion = _.extend({}, @secondDocUpdate, {version: "#{@version}.1"})
 					@DocumentManager.renameDocWithLock
-						.calledWith(@project_id, @firstDocUpdate.id, @user_id, @firstDocUpdate)
+						.calledWith(@project_id, @firstDocUpdate.id, @user_id, firstDocUpdateWithVersion)
 						.should.equal true
 					@DocumentManager.renameDocWithLock
-						.calledWith(@project_id, @secondDocUpdate.id, @user_id, @secondDocUpdate)
+						.calledWith(@project_id, @secondDocUpdate.id, @user_id, secondDocUpdateWithVersion)
 						.should.equal true
 
 				it "should rename the files in the updates", ->
+					firstFileUpdateWithVersion = _.extend({}, @firstFileUpdate, {version: "#{@version}.2"})
 					@ProjectHistoryRedisManager.queueRenameEntity
-						.calledWith(@project_id, 'file', @firstFileUpdate.id, @user_id, @firstFileUpdate)
+						.calledWith(@project_id, 'file', @firstFileUpdate.id, @user_id, firstFileUpdateWithVersion)
 						.should.equal true
 
 				it "should not flush the history", ->
@@ -72,7 +77,7 @@ describe "ProjectManager", ->
 				beforeEach ->
 					@error = new Error('error')
 					@DocumentManager.renameDocWithLock = sinon.stub().yields(@error)
-					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @callback
+					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
 
 				it "should call the callback with the error", ->
 					@callback.calledWith(@error).should.equal true
@@ -81,7 +86,7 @@ describe "ProjectManager", ->
 				beforeEach ->
 					@error = new Error('error')
 					@ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields(@error)
-					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @callback
+					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
 
 				it "should call the callback with the error", ->
 					@callback.calledWith(@error).should.equal true
@@ -89,7 +94,7 @@ describe "ProjectManager", ->
 			describe "with enough ops to flush", ->
 				beforeEach ->
 					@HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(true)
-					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @callback
+					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
 
 				it "should flush the history", ->
 					@HistoryManager.flushProjectChangesAsync
@@ -106,26 +111,36 @@ describe "ProjectManager", ->
 					docLines: "a\nb"
 				@docUpdates = [ @firstDocUpdate, @secondDocUpdate ]
 				@firstFileUpdate =
-					id: 2
+					id: 3
 					url: 'filestore.example.com/2'
-				@fileUpdates = [ @firstFileUpdate ]
+				@secondFileUpdate =
+					id: 4
+					url: 'filestore.example.com/3'
+				@fileUpdates = [ @firstFileUpdate, @secondFileUpdate ]
 				@ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields()
 
 			describe "successfully", ->
 				beforeEach ->
-					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @callback
+					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
 
 				it "should add the docs in the updates", ->
-					@ProjectHistoryRedisManager.queueAddEntity
-						.calledWith(@project_id, 'doc', @firstDocUpdate.id, @user_id, @firstDocUpdate)
+					firstDocUpdateWithVersion = _.extend({}, @firstDocUpdate, {version: "#{@version}.0"})
+					secondDocUpdateWithVersion = _.extend({}, @secondDocUpdate, {version: "#{@version}.1"})
+					@ProjectHistoryRedisManager.queueAddEntity.getCall(0)
+						.calledWith(@project_id, 'doc', @firstDocUpdate.id, @user_id, firstDocUpdateWithVersion)
 						.should.equal true
-					@ProjectHistoryRedisManager.queueAddEntity
-						.calledWith(@project_id, 'doc', @secondDocUpdate.id, @user_id, @secondDocUpdate)
+					@ProjectHistoryRedisManager.queueAddEntity.getCall(1)
+						.calledWith(@project_id, 'doc', @secondDocUpdate.id, @user_id, secondDocUpdateWithVersion)
 						.should.equal true
 
 				it "should add the files in the updates", ->
-					@ProjectHistoryRedisManager.queueAddEntity
-						.calledWith(@project_id, 'file', @firstFileUpdate.id, @user_id, @firstFileUpdate)
+					firstFileUpdateWithVersion = _.extend({}, @firstFileUpdate, {version: "#{@version}.2"})
+					secondFileUpdateWithVersion = _.extend({}, @secondFileUpdate, {version: "#{@version}.3"})
+					@ProjectHistoryRedisManager.queueAddEntity.getCall(2)
+						.calledWith(@project_id, 'file', @firstFileUpdate.id, @user_id, firstFileUpdateWithVersion)
+						.should.equal true
+					@ProjectHistoryRedisManager.queueAddEntity.getCall(3)
+						.calledWith(@project_id, 'file', @secondFileUpdate.id, @user_id, secondFileUpdateWithVersion)
 						.should.equal true
 
 				it "should not flush the history", ->
@@ -140,7 +155,7 @@ describe "ProjectManager", ->
 				beforeEach ->
 					@error = new Error('error')
 					@ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields(@error)
-					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @callback
+					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
 
 				it "should call the callback with the error", ->
 					@callback.calledWith(@error).should.equal true
@@ -149,7 +164,7 @@ describe "ProjectManager", ->
 				beforeEach ->
 					@error = new Error('error')
 					@ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields(@error)
-					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @callback
+					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
 
 				it "should call the callback with the error", ->
 					@callback.calledWith(@error).should.equal true
@@ -157,7 +172,7 @@ describe "ProjectManager", ->
 			describe "with enough ops to flush", ->
 				beforeEach ->
 					@HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(true)
-					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @callback
+					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
 
 				it "should flush the history", ->
 					@HistoryManager.flushProjectChangesAsync