September 7, 2007

Merging Previous Versions of Files Into Another Branch

Surround SCM
Version Control
Surround SCM's merge logic (which usually allows auto-merging of files without requiring the user to manually merge files) used in promoting must always promote the "tip" version of the files, this is the latest version of the files. In some occasions the version of the file that the needs to be promoted may not always be the "tip" version. The following article will show how you can take a specific set of files and a specific version and merge them into another branch. Unfortunately, this process will not be as easy or automatic as a promote or rebase since it does not use Surround SCM's advanced merge logic that is used for those actions. This will require manual merging of files to ensure that no lines of codes are taken out. Remember that no matter how smart a merge logic is, it can not be a substitute for a developer. This should not be considered as a common practice in Surround SCM. This is to be used in rare occasions. [toc]

Purpose

The sole purpose of this article is to address this specific scenario: Changes have been checked in to a baseline branch, and the changes have been tested and approved. They need to be promoted to a parent branch for a build/release. Only the promote was not done, and new changes have been checked in already to the baseline branch. You really can not promote because you do not want to include the new changes on the build/release. What to do? Your options are: 1.- Rollback all the new changes made and then promote. 2.- If that is not an option, get the versions of the files prior to the new change and merge then with the files on the parent branch. This article shows how to do option #2.

An Outline of the Process

The basic flow of this proposed process is first to identify the file(s) and the specific version from the source branch that need to be merged with the same file on another branch. This could be accomplished by using labels, changelists, time stamp, among others... Once the file versions have been identified, the next step is to get them to the corresponding working directory for the destination branch (branch where files will be merged). The files will then be merged and the results will be checked in to the destination branch. This essentially is a check in, only that instead of manually editing a file, the changes are merged.

The Example

For this example I am going to use a new C# project using Visual Studio 2005. Before I add anything to this project I add it to the mainline branch and I branch off immediately. I know have the same version of the project on the mainline branch and the baseline branch. The initial code of the "Form1.Designer.cs" file is as follows:
namespace SampleMergeProject
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Text = "Form1";
        }

        #endregion
    }
}
The same content exists on both branches. Now, I open the project that is bound to the mainline branch, and I add a button to the form. The file on the mainline branch now looks like this:
namespace SampleMergeProject
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            //
            // button1
            //
            this.button1.Location = new System.Drawing.Point(85, 119);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(146, 23);
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.UseVisualStyleBackColor = true;
            //
            // Form1
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(292, 273);
            this.Controls.Add(this.button1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.Button button1;
    }
}
Figure below shows the form on the mainline branch:

Figure 1 - Form on mainline branch

I then open the project that is bound to the baseline branch and add a text box to the main form. The Form1.Designer.cs file now looks like this:
namespace SampleMergeProject
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            //
            // textBox1
            //
            this.textBox1.Location = new System.Drawing.Point(76, 45);
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(100, 20);
            this.textBox1.TabIndex = 0;
            //
            // Form1
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(292, 273);
            this.Controls.Add(this.textBox1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.TextBox textBox1;
    }
}
Figure below shows the form on the baseline branch:

Figure 2 - Form on baseline branch

Upon check in of the files on the baseline branch, a comment is applied to mark this specific change. In the case of change sets, you could check in a group of files at the same time and check the box to "apply comments to all". I then reopen the project bound to the baseline branch and I add a combo box. Figure below shows the form on the baseline branch with the combo box:

Figure 3 - Form on baseline branch with combo box

So the form on the mainline branch contains a button and the form on the baseline branch contains a text box and a combo box. This represents two separate code lines where different changes are made on each branch. We now want to merge the text box into the form of the mainline branch project only. We are not ready to merge the combo box quite yet. In the scenario described above, the combo box represents the new change that we are not ready to promote quite yet, a scenario where you need to merge a selected new change to a file, instead of all the new changes to a file, which is what a promote would do. Since we properly placed a comment to mark the check in of the text box, we are now able to identify this specific change by the timestamp of the event. Another option would be to use labels (although they are not recommended for a branched SCM environment), a workflow state, or we could have used a changelist. The next step is to get the specified version of the files from the baseline branch to the mainline branch. For this, I right click on the Form1.Designer.cs file and select "Get" in the Surround SCM client. The following settings are set on the Get File dialog box: Location: The location is changed to the working directory of the same file on the mainline branch. Force File Retrieval From Server: This may not be necessary, but this ensures that the file is gotten regardless of what is in the working directory of the mainline branch. Get Based On: This changed from "Latest version" to "Timestamp". Since labels are not really recommended for a branched model, timestamps should be the way to go. To get the timestamp, simply view the history for the file and check the timestamp for the desired check in event. Proper use of comments should make identifying the proper check in event trivial. You could also change it to "Latest version that was in the state" if you want to merge changes that have been "approved" as opposed to changes that may still be "awaiting review". Getting the Files from a Changelist: If you marked the change using a changelist, the get is not as trivial. In the Changelist panel ("Tools" > "Changelists"), you can view committed changelists, and also perform a get of these files. The caveat is that the get goes to the working directory and this can not be changed. The problem then is that the files are gotten to the working directory of the baseline branch and not the mainline branch. There are two ways to address this: 1. Temporarily change the working directory of the baseline branch to be the working directory of the mainline branch. Get from the changelist console, and then switch the working directories back. This may be a dirty solution since it may give you working directory conflict and the .MySCMServerInfo files may get out of sync. 2. A 'cleaner' solution would be to get the files to the working directory of the baseline branch and then copy the files to the working directory for the mainline branch.

Merging the Changes

Now that we have the correct version of the file in the working directory of the mainline branch, we are ready to merge. The following steps are what I have found to work best. All these actions take place on the mainline branch. Step #1 - Check File Out Right click on the file and select "Check Out". The following options must be set in the Check Out File dialog box: Overwrite Option: This setting should be set to "Skip". This ensures that the check out action will not overwrite the version of the file we just placed there. Step #2 - Merge Files Right click on the file and select "Merge". The following options must be set in the Merge File Dialog box: Second File: Must be set to "Copy on server". The first file is the file in the working directory. Merge Should First attempt auto-merge: This must be unchecked. Otherwise the auto-merge will treat this like a check in and you may lose code. In this example, I would loose the button on the form. Now we are ready to click on the "Merge" button. Guiffy opens and it has shows the two files side by side. Above the two files, it has what the finished product would look like. Figure below illustrates this.

Figure 4 - Guiffy Merge Window

In this instance, Guiffy has selected the changes made on the mainline branch and has them crossed out. This is essentially what it would do in a check in action. However, we do not want to lose that change. To undo this, I set the cursor on the portion that is crossed out and then click on the "<<Undo>>" button. Figure below illustrates the cursor in blue by the change we want to undo.

Figure 5 - Change to undo

Figure below shows the results after the "<<Undo>>" button is clicked.

Figure 6 - Change reversed

This must be done for any change that we want to keep that is crossed out. After all the proper changes are merged and the merged file is saved, the file is checked in. Below is the file content after the merge:
namespace SampleMergeProject
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            // 

            // button1
            //
            this.button1.Location = new System.Drawing.Point(85, 119);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(146, 23);
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.UseVisualStyleBackColor = true;

            // textBox1
            //
            this.textBox1.Location = new System.Drawing.Point(76, 45);
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(100, 20);
            this.textBox1.TabIndex = 0;

            //
            // Form1
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(292, 273);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.textBox1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.TextBox textBox1;
    }
}
It contains code for the text box and the button. Since the get from the baseline was made based on timestamp, it does not include the combo box. Figure below shows the merged form

Figure 7 - Merged Form

Conclusion

As you can see this is not such a simple process, even with one file and one change on each branch. So, as stated above, this should not be thought of as a standard process. This should only be done in emergency situation where the promote was not performed when it should have and the latest versions of the files on the child branch now contain changes that are not ready to promote.