Skip to main content

My Experience moving FinOps/X++ code to GitHub

I recently saw a question on LinkedIn, asking if we can share our experience with moving to Git version control.

Here is a response to it. I will share my experience of moving to GitHub version control. We have chosen to move our X++ code to Git and in particular GitHub about 6 months ago.

Reasons for the move

Below are some of the reasons we have chosen to move from TFVC on Azure DevOps to Git on GitHub:
  1. Every single source code/project we have is on Git except for our X++ code. I have been asked way too many times as to why we are on TFVC. I have explained many times but I can't help shake the feeling that others think X++ is some old language. In other words, better alignment.
  2. There has been considerable effort to move to GitHub as our approved version control from many other version control systems. This is to make our code more accessible to all teams, have policies in place, leverage shared tooling, better manage onboarding experience etc.
  3. Better branching and merging experience


Below is the architecture we have gone with.

  1. Developers - we have multiple developers with their dedicated VM on our Azure subscription
  2. Jira - Our issue tracking (tasks, bugs etc) and any planning is done on Jira
  3. GitHub - Our version control where we keep our X++ source code, our pipeline code (yaml, powershell), postman collection
  4. Azure DevOps Build pipeline - we have chosen to have both forms of our build pipeline
    1. Build with Nuget via hosted agent - this is executed on every pull request to main to catch any merge issues that would result in a compile issue.
    2. Build via a build VM - this is executed in two various ways. Manually executed on demand when we wish to do a release and automatic at night when ever there is code change in main. We use the build VM because we have automated unit tests, automated RSAT runs and run CAR report that gets uploaded to the artifacts.
  5. Azure DevOps Release Pipelines - The release pipeline will take the deployable package and push to our test environment.


Here is the folder structure we keep in our repo. I am masking/obfuscating the names here and are to give you a general idea.


/projects/*/*.sln and *.proj


A few things to note here:
  1. I could have moved the Postman and PaymentConnector into its own repos but I found it cleaner to have it together. 
  2. .gitignore - I just used whats out there and found this one to be working well form me PaulHeisterkam/
  3. Build status - you will notice the build status badges I placed int he main readme. This gives clear visibility into the health of our pipelines. One that happens from time to time is getting a warning on the RSAT pipeline. We do have emails going to our team if the build fails too.


Our branching strategy is to have very short lived branches. 
  • main - The main branch which has a policy requiring an approval from another developer. Only way to get anything in is via a pull request
  • user/username/featurename - we like to place our dev branches under the developers (users) name. Easy to identify who is working on it and no one touches someone else's branch without communicating to them. Call it what ever you want, it can be called something generic like user/munib/dev. Its all yours and its all about creating frictionless development experience.
  • feature/featurename - this is a feature branch that is shared across multiple developers. It usually a large development that we may not release for multiple sprints. Give it something meaningful. As I am posting this, there are no feature branches in my current repo.
Currently, we don't have multiple releases. Only a single stream of development. If we had multiple release streams, I would make the main be in sync with BAU stream of work. Then create a release branch eg. release/Phase2

Symbolic link

We are using these powershell scripts for our symbolic links and works really well.

Build and Release piplelines

I have attempted to use GitHub workflow actions but it was a fair bit of work. Azure DevOps pipelines work really well with GitHub and there are already tasks that I can utilise. Where as in GitHub Actions, I would have had to write powershell scripts.

Jira and GitHub

Jira integrates with GitHub really nicely. When ever we commit in Git and put the issue number, it will automatically link the code change to the Jira issue.

Release notes

We create release notes via Jira. This is done by tagging the fixed version to the release. Nothing sophisticated here to avoid confusion. The process is simple and doesn't take that much effort.

Git commands

I am not a Git command person. I use a combination of commands and UI tools. I have experimented with the below and I still use a combination of them.
  • GitHub Desktop
  • VS Code - with Git extensions installed
  • Visual studio Git UI experience
  • Git command line
My advice to any developers is, use what is comfortable to you.

Onboarding developers

This is probably the biggest challenge in most companies. FinOps developers are so used to TFVC and its implicitly. Our daily ritual is Get latest, do some coding and checkin.
One way to overcome this is a good clean readme page. Simple instructions on how to get started. Assign a simple development task and walk them through it. 
Then the new ritual will be something like this
git checkout main
git fetch
git pull

git merge users/munib/dev main
git checkout users/munib/dev

Just build up slowly to more complex scenarios.


These links are super helpful

Popular posts from this blog

AX - How to use Map and MapEnumerator

Similar to Set class, Map class allows you to associate one value (the key) with another value. Both the key and value can be any valid X++ type, including objects. The types of the key and the value are specified in the declaration of the map. The way in which maps are implemented means that access to the values is very fast. Below is a sample code that sets and retrieves values from a map. static void checkItemNameAliasDuplicate(Args _args) { inventTable inventTable; Map map; MapEnumerator mapEnumerator; NameAlias nameAlias; int counter = 0; ; map = new Map(Types::String, Types::Integer); //store into map while select inventTable { nameAlias = inventTable.NameAlias; if (!map.exists(nameAlias)) { map.insert(nameAlias, 1); } else { map.insert(nameAlias, map.lookup(nameAlias) + 1); } } //retrieve fro

AX - How to use Set and SetEnumerator

The Set class is used for the storage and retrieval of data from a collection in which the values of the elements contained are unique and serve as the key values according to which the data is automatically ordered. You can create a set of primitive data types or complex data types such as a Class, Record or Container. Below is sample of a set of records. static void _Set(Args _args) {     CustTable       custTable;     Set             set = new Set(Types::Record);     SetEnumerator   setEnumerator;     ;     while select custTable     {         if (custTable && !         {             set.add(custTable);         }     }     if (!set.empty())     {         setEnumerator = set.getEnumerator();         setEnumerator.reset();         while (setEnumerator.moveNext())         {             custTable = setEnumerator.current();             info(strfmt("Customer: %1",custTable.AccountNum));         }     } } Common mistake when creating a set of recIds

Import document handling (attachment) files #MSDyn365FO

Out of the box you have limited data entities for migrating attachments. If you search what is already in the AOT, you will see a few various examples. I suggest you look at the LedgerJournalAttachmentsEntity as it is the simplest and cleans to copy from. I wont go into detail but I will give a quick run down of what it looks like. Use the DocuRefEntity as your main datasource. It does most of the work for you. Set your table you want to import for as the child datasource Add the Key You will need to add the postLoad method. There is minor code to update the virtual field FileContents. Below is an export I did for the general journal attachments. The import zip structure should be the same way. It will create the usual artifacts such as the excel, manifest and package header xml files. You will see a Resources folder under that. If you drill down to the resources you will see the attachments. This is an export and it used the document GUID for uniqueness. The other thing is the extensi