git for ICU Developers

For git & git lfs installation see the Source Code Access page.

For setup with Eclipse, Xcode, etc. see the Setup page and its subpages.


ICU development is on GitHub, in the main branch of the git repository.

In preparation for a release, we create a maintenance branch, such as maint/maint-62 for ICU 62 and its maintenance releases.

For each release we create a release tag. releases/tag/release-62-1 (GitHub project page > Releases > Tags > select one; a Release is a Tag with metadata.)

There are additional branches that you can ignore. Some are old development branches.

Also, when you edit a file directly on the GitHub source browser (for docs: API comments, or .md/.html/.txt), it creates a branch for your pull request. Make sure to delete this branch when you are done.


We do not develop directly on the main repository. Do not clone from there to commit and push back into the main repository.

Instead, use the GitHub UI (top right) to create a fork of the repository in your own GitHub account. Then clone that to your local machine. You need only one fork for all of your ICU work.


mkdir -p icu/mine/src

git clone icu/mine/src

cd icu/mine/src

You should be in the main branch of your fork's clone.

Do not do any development in your own main branch either! That would lead to messy merging with the upstream main branch.

Instead, create a new branch in your local clone for each piece of work. You need a separate branch for each pull request. More on that later.

Linux: git checkout -b mybranchname

Now you are in a new development branch in your local git repo. Confirm with git status. Change stuff. Do git status again, use git add for staging and git commit -m 'ICU-23456 what I changed' to commit, or use git commit -a -m 'ICU-23456 what I changed' if you want to commit everything that git status shows as changed.

For looking at changes, you should set up a visual diff program for use with git difftool. See the Setup: git difftool & mergetool page.

For new files: Remember to add the appropriate copyright lines. Copy from a file of the same type, and set the copyright year to the current year (that is, the year you are creating the file).

You should have a Jira ticket for each line of work. (See Submitting ICU Bugs and Feature Requests and ICU Ticket Life cycle.) You can have multiple pull requests per ticket. Each pull request needs a ticket in Accepted state.

Always prefix your commit statements with the Jira ticket number using this pattern (including the space after the number; note: no colon): ICU-23456 what I changed

Local commits are only on your local machine. If your local disk crashes, your changes are gone. git push your commits to your GitHub fork.

Tips for Branches

Shane recommends setting the default behavior of "git pull" to --ff-only. Shane also prevents local commits to the main branch via .git/hooks/pre-commit. These two measures make it easier to do the right thing in Git.

Trivial changes

For trivial changes, such as small fixes in API docs or text files, it is ok to edit the file in the GitHub GUI, in the main unicode-org/icu repository.

You still need a Jira ticket.

Once you are done editing, the GUI lets you create a branch and a commit right in the main repository. Use the usual ICU-23456 what I changed pattern for the commit message.

Pull request, review, merge as usual, see the next section.

Remember to delete your branch after merging.

Review & commit to Unicode main

When you are ready for code review, go to your GitHub page and your ICU fork.

Select your dev branch (Branch drop-down on the left, search for your branch).

Click "New pull request" next to the Branch button, or "Pull request" on the right near "Compare". Make sure it compares with unicode-org/icu main on the left and your own fork's dev branch on the right.

Prefix the title of your pull request with the Jira ticket number, same format as for a commit.

Follow the rest of the checklist in the PR template.

Set the PR assignee to your main reviewer. You may add more people as reviewers, but there is normally just one assignee. Be somewhat judicious with additional reviewers: Don't just add them because they were recommended by GitHub.

Nice to have: Optionally set the Jira ticket reviewer field for documentation. Still possible to close the ticket if the field is empty.

Watch the PR status for build failures and other issues.

A PR reviewer (at least the assignee) should look to see if the PR does what the ticket says.

Respond to review feedback. Make changes on your local machine, commit, push to your fork. The GitHub PR will update automatically for your additional commits.

Try to not rebase, squash, or force-push until the reviewer gives you a green light.

You should normally squash multiple commits into one in your fork before merging (after the reviewer is satisfied).  For multiple commits, the reviewer should first respond with something like "lgtm please squash" but not yet GitHub-approve; after squashing, they should check that the changes are the same, and then GitHub-approve.  A bot will respond to the PR confirming whether the squash succeeded without changing the file contents.

If you squash, since you are rewriting the commit message anyway, please append the pull request number to the first line of the updated message, using the format " (#199)".

When you squash, please keep the parent hash (sha) the same so that the squash is nothing more than a squash.  If you change the parent hash, you may also be pulling in other people's changes, and it may be harder for the reviewer to verify that the squash was done correctly.

Options on how to squash:

Option 1: Use the online PR commit checker bot.  Please note: this makes the change in your remote branch but not in your local branch.  Click the "Details" link in the GitHub status, which brings you to a page with a summary of your PR.  Find the "Squash..." button.  Sign in using your GitHub account, and follow the flow to squash your branch.

Warning: do not git pull after you use the remote tool!  If you subsequently need to update your local branch to the squash commit, you need to fetch and reset:

git fetch origin BRANCHNAME

git checkout BRANCHNAME

git reset origin/BRANCHNAME

Option 2: Use git rebase.  This works as long as you have no merge commits with conflicts in your history.  Plenty of examples:

Option 3: Use git merge.  This is a little tricker but works even if you have merge commits with conflicts.  Assuming your feature branch is called BRANCHNAME:

# Make sure your branch is up-to-date with main and that the tests pass:

git checkout BRANCHNAME

git merge main

git push

# At this point, wait for an LGTM from a reviewer before proceeding.

# Once confirmed, make your squash commit in a new temp branch.

# NOTE: In the first line, make sure to checkout the same sha as

# you most recently merged into your branch!

git checkout main

git checkout -b temp

git merge --squash BRANCHNAME

git commit

# Point your branch to the squash commit, and there should be no dirty files:

git checkout BRANCHNAME

git reset temp

git status  # should be empty! If it's not, you didn't check out the right sha.

# Push your squash commit and clean up:

git push -f

git branch -d temp

Option 4: When making code review changes on a small PR, you can amend your previous commit rather than making a new commit.  Instead of running "git commit", just run "git commit --amend".  You will need to force-push.  The PR bot will post a link for the reviewer to see the changes from your old commit to your new commit.

Once the reviewer(s) has/have approved your (squashed) changes:

Merge conflicts

When someone else has made changes that conflict with yours, then you can't merge as is. (The GitHub pull request page will tell you if there is a conflict.)

You need to update your fork's main via your local clone, rebase your local dev branch with that, resolve conflicts as you go, and force-push to your fork.

As easy as it is in GitHub to create a fork, you would think that it would be a simple button-click to update your fork's main with commits on the Unicode main. If you find a way to do this, please update this section.

Switch to your local main.

git checkout main

Pull updates from the Unicode main (rather than a vanilla git pull which pulls form your out-of-date fork), push to your fork's main.

Norbert’s version:

git pull

git push

Andy’s version:

Once per local git repo, set up an additional "remote". Something like the following, but this may be incomplete!

git remote add upstream

git pull upstream main

git push origin main

Andy's Version, take 2: 

Set the local main to track the upstream (unicode-org) main instead of your fork's main (orign). Your fork's main is effectively out of the loop.

# one time setup

git branch -u upstream/main

# subsequent pulls from upstream ( main

git pull

Now, resolve the conflicts.

There are two ways to do this.  You can rebase, or you can create a merge commit.  The advantage of rebase is that it makes it somewhat easier to squash later on.  The advantage of creating a merge commit is that you don't have to force-push, so it makes it easier to work across different workstations, you are less likely to get something wrong, and it makes it easier for the reviewer because GitHub keeps track of comment history better when shas don't change.

Option 1: Merge. Switch to your dev branch, then merge in main.  I like to use the --no-commit option:

git checkout mybranchname

git merge main --no-commit

If you have conflicts, resolve them.  Then, review the merge commit.  It should have all changes from main that were not yet on your branch.  If it looks good, commit the merge.  You can push the merge commit without having to use -f.

git commit

git push

Option 2: Rebase. First switch back to your dev branch (without the -b option which is for creating a new branch).

git checkout mybranchname

Then rebase, which reapplies your branch changes on top of the new main commits.

git rebase main

Sometimes you need to manually resolve conflicts. Follow the instructions git prints or look for help...

If it had stopped and you are done resolving conflicts, continue rebasing.

git rebase --continue

You might get conflicts at several stages; resolve & continue until done.

When done, push to your GitHub fork. You need to force-push after rebasing.

git push -f

Update your fork

Once in a while, you should update your fork's main with changes from the Unicode main, so that you don't fall too far behind and your new changes don't create unnecessary merge conflicts.

Go to your local main, pull commits from the Unicode main, and push to your GitHub fork. See the "Merge conflicts" section above for details. If you don't have a current dev branch, you can skip the rebasing.

Committing to Maintenance Branch

Follow these steps for adding a commit to a maintenance branch.

The process is different between when we are between RC and GA and when we are after GA.

Between RC and GA: When working on a commit that you know at the time of authorship to be a candidate for the maintenance branch, write the commit and send the PR directly against the maintenance branch.  All commits on the maint branch will be merged from maint to main as a BRS task (see the next section).

Check out the current maint branch:

git fetch upstream maint/maint-64

git checkout maint/maint-64

Next, make a local branch off of the maint branch. For example, to use the branch name "ICU-12345-maint-64", you can do:

git checkout -b ICU-12345-maint-64

Now, write your change and send it for review.  Open your PR against the maint branch.

After GA: Write the commit against the main branch, and send your own cherry-pick commits to put it on the desired maint branches.

Update your local main from the Unicode main (see above). Otherwise your git workspace won't recognize the commits you are trying to cherry-pick.

Make a note of the SHA hash/ID of your commit on the  main branch. You will use this later when cherry-picking into the maint branch.

Next, checkout the maintenance branch locally.  For example, for the ICU 63 maintenance branch:

git fetch upstream maint/maint-64

git checkout maint/maint-64

Next, make a local branch off of the maint branch. This new branch will be used for your cherry-pick.

For example, to use the branch name "ICU-12345-maint-64", you can do:

git checkout -b ICU-12345-maint-64

Next, cherry-pick the commit(s) you want to apply to the maintenance branch.  (Note: If you only have one commit to merge to the  maint  branch then you would only have one command below).

git cherry-pick 7d99ba4

git cherry-pick e578f3f


This creates new commits directly onto your local branch.

Look at the output from each of these commands to double-check that you got the intended commits.

Finally, push your branch to your fork (should be "origin"), and open a PR into the Unicode ICU branch maint/maint-64.

git push -u your-fork ICU-12345-maint-64

The reviewer of the PR has the following special responsibilities:

Checking for Missing Commits (BRS Task)

It is not hard to accidentally make a commit against main that should have been against maint.  As a BRS task before tagging, you should check the list of commits that are on main but not maint and make sure none of them belong on maint.

To get the list, run:

git fetch upstream

git cherry -v upstream/maint/maint-64 upstream/main

Commits prefixed with "+" are on main but not on the specified maint branch.  Commits prefixed with "-" are present on both branches.

Send the list to the team and discuss in the weekly meeting if there are any problems.

Merging from Maint to Main (BRS Task)

Merging from the maint branch to main might be as easy as opening a pull request, without having to touch the command line.  However, if there are merge conflicts, more work will need to be done.

The Easy Way (No Merge Conflicts): Open a pull request on GitHub from the maint branch to main.  If it says there are no merge conflicts, congratulations!  Use a new ticket number for the PR (it is suggested to NOT use the main BRS ticket).  The new ticket should have the next release as its fix version, because the merge commit used to pull the commits from maint to main will be in the next release but not the current release.

You may need to add "DISABLE_JIRA_ISSUE_MATCH=true" and/or "ALLOW_MANY_COMMITS=true" to the PR description to silence errors coming from the Unicode bot.

You should use a MERGE COMMIT to merge from maint to main, NOT REBASE MERGE as is normally recommended.  You will need to go into the admin panel on GitHub, enable merge commits, perform your merge commit, and then disable merge commits again from the admin panel.  When making your merge commit, remember to use the correct commit message syntax: prefix the merge commit message with ICU-#####, the new ticket number you created above.

The Hard Way (Merge Conflicts): At the end of the day, the goal is that main should share the maint branch's history.  This is done using merge commits.  What follows is an example of how to create merge commits that retain full branch history.

Create a new branch based on the tag you want to merge:

git fetch upstream

git checkout main

git checkout -b 64-merge-branch  # use any name you like

If you already have this branch from a previous release tag, you could either use a new branch, or merge the latest main into your branch:

git checkout 64-merge-branch

# DANGER: Please make sure your workspace is clean before proceeding!

# If it's not, you might sneak in unreviewed changes.

git merge --no-commit main

git commit -am "ICU-##### Merge tag 'main' into 64-merge-branch"

Now, merge in maint:

# DANGER: Please make sure your workspace is clean before proceeding!

# If it's not, you might sneak in unreviewed changes.

git merge --no-commit upstream/maint/maint-39

After running the final line, you will have the opportunity to resolve merge conflicts.  If the conflict is in a large binary file like the ICU4J data jar files, you may need to re-generate them.

Remember to prefix your commit message with the ticket number:

git commit -am "ICU-##### Merge branch 'maint/maint-39' into 64-merge-branch"

git push -u origin 64-merge-branch

As in the Easy Way, you may need to add "DISABLE_JIRA_ISSUE_MATCH=true" and/or "ALLOW_MANY_COMMITS=true" to the PR description to silence errors coming from the Unicode bot.

Send the PR off for review.  As in the Easy Way, you should use the MERGE COMMIT option in GitHub to land the PR!!

Manually Landing PRs

There may be times when a PR is landed manually. This dangerous topic is covered in a separate subpage.

Requesting an Exhaustive Test run on a Pull-Request (PR)

The ICU4C and ICU4J Exhaustive Tests run on the main branch after a pull-request has been submitted. They do not run on pull-requests by default as they take 1-2 hours to run.

However, you can manually request the CI builds to run the exhaustive tests on a PR by commenting with the following text:

  /azp run CI-Exhaustive

This will trigger the test run on the PR. This is covered more in a separate document.