[{"data":1,"prerenderedAt":719},["ShallowReactive",2],{"/en-us/blog/automating-boring-git-operations-gitlab-ci/":3,"navigation-en-us":36,"banner-en-us":464,"footer-en-us":481,"Kristian Larsson":691,"next-steps-en-us":704},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":26,"_id":29,"_type":30,"title":31,"_source":32,"_file":33,"_stem":34,"_extension":35},"/en-us/blog/automating-boring-git-operations-gitlab-ci","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"GitBot – automating boring Git operations with CI","Guest author Kristian Larsson shares how he automates some common Git operations, like rebase, using GitLab CI.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749672374/Blog/Hero%20Images/gitbot-automate-git-operations.jpg","https://about.gitlab.com/blog/automating-boring-git-operations-gitlab-ci","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"GitBot – automating boring Git operations with CI\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Kristian Larsson\"}],\n        \"datePublished\": \"2017-11-02\",\n      }",{"title":9,"description":10,"authors":17,"heroImage":11,"date":19,"body":20,"category":21,"tags":22},[18],"Kristian Larsson","2017-11-02","Git is super useful for anyone doing a bit of development work or just\ntrying to\n\nkeep track of a bunch of text files. However, as your project grows you\nmight\n\nfind yourself doing lots of boring repetitive work just around Git itself.\nAt\n\nleast that’s what happened to me and so I automated some boring Git stuff\nusing our\n\n[continuous integration (CI) system](/solutions/continuous-integration/).\n\n\n\u003C!-- more -->\n\n\nThere are probably all sorts of use cases for automating various Git\noperations\n\nbut I’ll talk about a few that I’ve encountered. We’re using GitLab and\n[GitLab\n\nCI](/solutions/continuous-integration/) so that’s what my examples\n\nwill include, but most of the concepts should apply to other systems as\nwell.\n\n\n## Automatic rebase\n\n\nWe have some Git repos with source code that we receive from vendors, who we\ncan think\n\nof as our `upstream`. We don’t actually share a Git repo with the vendor but\n\nrather we get a tar ball every now and then. The tar ball is extracted into\na\n\nGit repository, on the `master` branch which thus tracks the software as it\nis\n\nreceived from upstream. In a perfect world the software we receive would be\n\nfeature complete and bug free and so we would be done, but that’s usually\nnot\n\nthe case. We do find bugs and if they are blocking we might decide to\nimplement\n\na patch to fix them ourselves. The same is true for new features where we\nmight\n\nnot want to wait for the vendor to implement it.\n\n\nThe result is that we have some local patches to apply. We commit such\npatches\n\nto a separate branch, commonly named `ts` (for TeraStream), to keep them\n\nseparate from the official software. Whenever a new software version is\nreleased,\n\nwe extract its content to `master` and then rebase our `ts` branch onto\n`master`\n\nso we get all the new official features together with our patches. Once\nwe’ve\n\nimplemented something we usually send it upstream to the vendor for\ninclusion.\n\nSometimes they include our patches verbatim so that the next version of the\ncode\n\nwill include our exact patch, in which case a rebase will simply skip our\npatch.\n\nOther times there are slight or major (it might be a completely different\ndesign)\n\nchanges to the patch and then someone typically needs to sort out the\npatches\n\nmanually. Mostly though, rebasing works just fine and we don’t end up with\nconflicts.\n\n\nNow, this whole rebasing process gets a tad boring and repetitive after a\nwhile,\n\nespecially considering we have a dozen of repositories with the setup\ndescribed\n\nabove. What I recently did was to automate this using our CI system.\n\n\nThe workflow thus looks like:\n\n\n- human extracts zip file, git add + git commit on master + git push\n\n- CI runs for `master` branch\n   - clones a copy of itself into a new working directory\n   - checks out `ts` branch (the one with our patches) in working directory\n   - rebases `ts` onto `master`\n   - push `ts` back to `origin`\n- this event will now trigger a CI build for the `ts` branch\n\n- when CI runs for the `ts` branch, it will compile, test and save the\nbinary output as “build artifacts”, which can be included in other\nrepositories\n\n- GitLab CI, which is what we use, has a CI_PIPELINE_ID that we use to\nversion built container images or artifacts\n\n\nTo do this, all you need is a few lines in a .gitlab-ci.yml file,\nessentially;\n\n\n```\n\nstages:\n  - build\n  - git-robot\n\n... build jobs ...\n\n\ngit-rebase-ts:\n  stage: git-robot\n  only:\n    - master\n  allow_failure: true\n  before_script:\n    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'\n    - eval $(ssh-agent -s)\n    - ssh-add \u003C(echo \"$GIT_SSH_PRIV_KEY\")\n    - git config --global user.email \"kll@dev.terastrm.net\"\n    - git config --global user.name \"Mr. Robot\"\n    - mkdir -p ~/.ssh\n    - cat gitlab-known-hosts >> ~/.ssh/known_hosts\n  script:\n    - git clone git@gitlab.dev.terastrm.net:${CI_PROJECT_PATH}.git\n    - cd ${CI_PROJECT_NAME}\n    - git checkout ts\n    - git rebase master\n    - git push --force origin ts\n  ```\n\nWe’ll go through the Yaml file a few lines at a time. Some basic knowledge\nabout GitLab CI is assumed.\n\n\nThis first part lists the stages of our pipeline.\n\n\n```\n\nstages:\n  - build\n  - git-robot\n  ```\n\nWe have two stages, first the `build` stage, which does whatever you want it\nto\n\ndo (ours compiles stuff, runs a few unit tests and packages it all up), then\nthe\n\n`git-robot` stage which is where we perform the rebase.\n\n\nThen there’s:\n\n\n```\n\ngit-rebase-ts:\n  stage: git-robot\n  only:\n    - master\n  allow_failure: true\n  ```\n\nWe define the stage in which we run followed by the only statement which\nlimits\n\nCI jobs to run only on the specified branch(es), in this case `master`.\n\n\n`allow_failure` simply allows the CI job to fail but still passing the\npipeline.\n\n\nSince we are going to clone a copy of ourselves (the repository checked out\nin\n\nCI) we need SSH and SSH keys set up. We’ll use ssh-agent with a\npassword-less key\n\nto authenticate. Generate a key using ssh-keygen, for example:\n\n\n```\n\nssh-keygen\n\n\nkll@machine ~ $ ssh-keygen -f foo\n\nGenerating public/private rsa key pair.\n\nEnter passphrase (empty for no passphrase):\n\nEnter same passphrase again:\n\nYour identification has been saved in foo.\n\nYour public key has been saved in foo.pub.\n\nThe key fingerprint is:\n\nSHA256:6s15MZJ1/kUsDU/PF2WwRGA963m6ZSwHvEJJdsRzmaA kll@machine\n\nThe key's randomart image is:\n\n+---[RSA 2048]----+\n\n|            o**.*|\n\n|           ..o**o|\n\n|           Eo o%o|\n\n|          .o.+o O|\n\n|        So oo.o+.|\n\n|       .o o.. o+o|\n\n|      .  . o..o+=|\n\n|     . o ..  .o= |\n\n|      . +.    .. |\n\n+----[SHA256]-----+\n\nkll@machine ~ $\n\n```\n\n\nAdd the public key as a deploy key under Project Settings\n\n\u003Ci class=\"fas fa-arrow-right\" aria-hidden=\"true\">\u003C/i> Repository \u003Ci\nclass=\"fas fa-arrow-right\" aria-hidden=\"true\">\u003C/i>\n\nDeploy Keys. Make sure you enable write access or you won’t be able to have\nyour\n\nGit robot push commits. We then need to hand over the private key so that it\ncan\n\nbe accessed from within the CI job. We’ll use a secret environment variable\nfor\n\nthat, which you can define under Project Settings\n\n\u003Ci class=\"fas fa-arrow-right\" aria-hidden=\"true\">\u003C/i> Pipelines \u003Ci\nclass=\"fas fa-arrow-right\" aria-hidden=\"true\">\u003C/i>\n\nEnvironment variables). I’ll use the environment variable GIT_SSH_PRIV_KEY\nfor this.\n\n\nNext part is the before_script:\n\n\n```\n  before_script:\n    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'\n    - eval $(ssh-agent -s)\n    - ssh-add \u003C(echo \"$GIT_SSH_PRIV_KEY\")\n    - git config --global user.email \"kll@dev.terastrm.net\"\n    - git config --global user.name \"Mr. Robot\"\n    - mkdir -p ~/.ssh\n    - cat gitlab-known-hosts >> ~/.ssh/known_hosts\n  ```\n\nFirst ssh-agent is installed if it isn’t already. We then start up ssh-agent\nand\n\nadd the key stored in the environment variable GIT_SSH_PRIV_KEY (which we\nset up\n\npreviously). The Git user information is set and we finally create .ssh and\nadd\n\nthe known host information about our GitLab server to our known_hosts file.\nYou\n\ncan generate the gitlab-known-hosts file using the following command:\n\n\n```\n\nssh-keyscan my-gitlab-machine >> gitlab-known-hosts\n\n```\n\n\nAs the name implies, the before_script is run before the main `script` part\nand\n\nthe ssh-agent we started in the before_script will also continue to run for\nthe\n\nduration of the job. The ssh-agent information is stored in some environment\n\nvariables which are carried across from the before_script into the main\nscript,\n\nenabling it to work. It’s also possible to put this SSH setup in the main\nscript,\n\nI just thought it looked cleaner splitting it up between before_script and\nscript.\n\nNote however that it appears that after_script behaves differently so while\nit’s\n\npossible to pass environment vars from before_script to script, they do not\n\nappear to be passed to after_script. Thus, if you want to do Git magic in\nthe\n\nafter_script you also need to perform the SSH setup in the after_script.\n\n\nThis brings us to the main script. In GitLab CI we already have a\nchecked-out\n\nclone of our project but that was automatically checked out by the CI system\n\nthrough the use of magic (it actually happens in a container previous to the\none\n\nwe are operating in, that has some special credentials) so we can’t really\nuse\n\nit, besides, checking out other branches and stuff would be really weird as\nit\n\ndisrupts the code we are using to do this, since that’s available in the Git\n\nrepository that’s checked out. It’s all rather meta.\n\n\nAnyway, we’ll be checking out a new Git repository where we’ll do our work,\nthen\n\nchange the current directory to the newly checked-out repository, after\nwhich\n\nwe’ll check out the `ts` branch, do the rebase and push it back to the\norigin remote.\n\n\n```\n    - git clone git@gitlab.dev.terastrm.net:${CI_PROJECT_PATH}.git\n    - cd ${CI_PROJECT_NAME}\n    - git checkout ts\n    - git rebase master\n    - git push --force origin ts\n  ```\n\n… and that’s it. We’ve now automated the rebasing of a branch in our config\nfile. Occasionally it\n\nwill fail due to problems rebasing (most commonly merge conflicts) but then\nyou\n\ncan just step in and do the above steps manually and be interactively\nprompted\n\non how to handle conflicts.\n\n\n## Automatic merge requests\n\n\nAll the repositories I mentioned in the previous section are NEDs, a form of\n\ndriver for how to communicate with a certain type of device, for Cisco NSO\n(a\n\nnetwork orchestration system). We package up Cisco NSO, together with these\nNEDs\n\nand our own service code, in a container image. The build of that image is\n\nperformed in CI and we use a repository called `nso-ts` to control that\nwork.\n\n\nThe NEDs are compiled in CI from their own repository and the binaries are\nsaved\n\nas build artifacts. Those artifacts can then be pulled in the CI build of\n`nso-ts`.\n\nThe reference to which artifact to include is the name of the NED as well as\nthe\n\nbuild version. The version number of the NED is nothing more than the\npipeline\n\nid (which you’ll access in CI as ${CI_PIPELINE_ID}) and by including a\nspecific\n\nversion of the NED, rather than just use “latest” we gain a much more\nconsistent\n\nand reproducible build.\n\n\nWhenever a NED is updated a new build is run that produces new binary\nartifacts.\n\nWe probably want to use the new version but not before we test it out in CI.\nThe\n\nactual versions of NEDs to use is stored in a file in the `nso-ts`\nrepository and\n\nfollows a simple format, like this:\n\n\n```\n\nned-iosxr-yang=1234\n\nned-junos-yang=4567\n\n...\n\n```\n\n\nThus, updating the version to use is a simple job to just rewrite this text\nfile\n\nand replace the version number with a given CI_PIPELINE_ID version number.\nAgain,\n\nwhile NED updates are more seldom than updates to `nso-ts`, they do occur\nand\n\nhandling it is bloody boring. Enter automation!\n\n\n```\n\ngit-open-mr:\n  image: gitlab.dev.terastrm.net:4567/terastream/cisco-nso/ci-cisco-nso:4.2.3\n  stage: git-robot\n  only:\n    - ts\n  tags:\n    - no-docker\n  allow_failure: true\n  before_script:\n    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'\n    - eval $(ssh-agent -s)\n    - ssh-add \u003C(echo \"$GIT_SSH_PRIV_KEY\")\n    - git config --global user.email \"kll@dev.terastrm.net\"\n    - git config --global user.name \"Mr. Robot\"\n    - mkdir -p ~/.ssh\n    - cat gitlab-known-hosts >> ~/.ssh/known_hosts\n  script:\n    - git clone git@gitlab.dev.terastrm.net:TeraStream/nso-ts.git\n    - cd nso-ts\n    - git checkout -b robot-update-${CI_PROJECT_NAME}-${CI_PIPELINE_ID}\n    - for LIST_FILE in $(ls ../ned-package-list.* | xargs -n1 basename); do NED_BUILD=$(cat ../${LIST_FILE}); sed -i packages/${LIST_FILE} -e \"s/^${CI_PROJECT_NAME}.*/${CI_PROJECT_NAME}=${NED_BUILD}/\"; done\n    - git diff\n    - git commit -a -m \"Use ${CI_PROJECT_NAME} artifacts from pipeline ${CI_PIPELINE_ID}\"\n    - git push origin robot-update-${CI_PROJECT_NAME}-${CI_PIPELINE_ID}\n    - HOST=${CI_PROJECT_URL} CI_COMMIT_REF_NAME=robot-update-${CI_PROJECT_NAME}-${CI_PIPELINE_ID} CI_PROJECT_NAME=TeraStream/nso-ts GITLAB_USER_ID=${GITLAB_USER_ID} PRIVATE_TOKEN=${PRIVATE_TOKEN} ../open-mr.sh\n```\n\n\nSo this time around we check out a Git repository into a separate working\n\ndirectory again, it’s just that it’s not the same Git repository as we are\n\nrunning on simply because we are trying to do changes to a repository that\nis\n\nusing the output of the repository we are running on. It doesn’t make much\nof a\n\ndifference in terms of our process. At the end, once we’ve modified the\nfiles we\n\nare interested in, we also open up a merge request on the target repository.\n\nHere we can see the MR (which is merged already) to use a new version of the\n\nNED `ned-snabbaftr-yang`.\n\n\n\u003Cimg src=\"/images/blogimages/gitbot-ned-update-mr.png\" alt=\"MR using new\nversion of NED\" style=\"width: 700px;\"/>{: .shadow}\n\n\nWhat we end up with is that whenever there is a new version of a NED, a\nsingle merge\n\nrequest is opened on our `nso-ts` repository to start using the new NED.\nThat\n\nmerge request is using changes on a new branch and CI will obviously run for\n\n`nso-ts` on this new branch, which will then test all of our code using the\nnew\n\nversion of the NED. We get a form of version pinning, with the form of\nexplicit\n\nchanges that it entails, yet it’s a rather convenient and non-cumbersome\n\nenvironment to work with thanks to all the automation.\n\n\n## Getting fancy\n\n\nWhile automatically opening an MR is sweet… we can do ~~better~~fancier. Our\n`nso-ts`\n\nrepository is based on Cisco NSO (Tail-F NCS), or actually the `nso-ts`\nDocker\n\nimage is based on a `cisco-nso` Docker image that we build in a separate\n\nrepository. We put the version of NSO as the tag of the `cisco-nso` Docker\n\nimage, so `cisco-nso:4.2.3` means Cisco NSO 4.2.3. This is what the `nso-ts`\n\nDockerfile will use in its `FROM` line.\n\n\nUpgrading to a new version of NCS is thus just a matter of rewriting the\ntag…\n\nbut what version of NCS should we use? There’s 4.2.4, 4.3.3, 4.4.2 and 4.4.3\n\navailable and I’m sure there’s some other version that will pop up its evil\n\nhead soon enough. How do I know which version to pick? And will our current\ncode\n\nwork with the new version?\n\n\nTo help myself in the choice of NCS version I implemented a script that gets\nthe\n\nREADME file of a new NCS version and cross references the list of fixed\nissues\n\nwith the issues that we currently have open in the Tail-F issue tracker. The\n\noutput of this is included in the merge request description so when I look\nat\n\nthe merge request I immediately know what bugs are fixed or new features are\n\nimplemented by moving to a specific version. Having this automatically\ngenerated\n\nfor us is… well, it’s just damn convenient. Together with actually testing\nour\n\ncode with the new version of NCS gives us confidence that an upgrade will be\nsmooth.\n\n\nHere are the merge requests currently opened by our GitBot:\n\n\n\u003Cimg src=\"/images/blogimages/automate-git-merge-requests.png\" alt=\"Merge\nrequests automated by Git bot\" style=\"width: 700px;\"/>{: .shadow}\n\n\nWe can see how the system have generated MRs to move to all the different\n\nversions of NSO currently available. As we are currently on NSO v4.2.3\nthere’s\n\nno underlying branch for that one leading to an errored build. For the other\n\nversions though, there is a branch per version that executes the CI pipeline\nto\n\nmake sure all our code runs with this version of NSO.\n\n\nAs there have been a few commits today, these branches are behind by six\ncommits\n\nbut will be rebased this night so we get an up-to-date picture if they work\nor\n\nnot with our latest code.\n\n\n\u003Cimg src=\"/images/blogimages/automate-git-commits.png\" alt=\"Commits\"\nstyle=\"width: 700px;\"/>{: .shadow}\n\n\nIf we go back and look at one of these merge requests, we can see how the\n\ndescription includes information about what issues that we currently have\nopen\n\nwith Cisco / Tail-F would be solved by moving to this version.\n\n\n\u003Cimg src=\"/images/blogimages/automate-git-mr-description.png\" alt=\"Merge\nrequest descriptions\" style=\"width: 700px;\"/>{: .shadow}\n\n\nThis is from v4.2.4 and as we are currently on v4.2.3 we can see that there\nare\n\nonly a few fixed issues.\n\n\nIf we instead look at v4.4.3 we can see that the list is significantly\nlonger.\n\n\n\u003Cimg src=\"/images/blogimages/automate-git-mr-description-list.png\"\nalt=\"Merge request descriptions\" style=\"width: 700px;\"/>{: .shadow}\n\n\nPretty sweet, huh? :)\n\n\nAs this involves a bit more code I’ve put the relevant files in a [GitHub\ngist](https://gist.github.com/plajjan/42592665afd5ae045ee36220e19919aa).\n\n\n## This is the end\n\n\nIf you are reading this, chances are you already have your reasons for why\nyou\n\nwant to automate some Git operations. Hopefully I’ve provided some\ninspiration\n\nfor how to do it.\n\n\nIf not or if you just want to discuss the topic in general or have more\nspecific\n\nquestions about our setup, please do reach out to me on\n[Twitter](https://twitter.com/plajjan).\n\n\n_[This post](http://plajjan.github.io/automating-git/) was originally\npublished on [plajjan.github.io](http://plajjan.github.io/)._\n\n\n## About the Guest Author\n\n\nKristian Larsson is a network automation systems architect at Deutsche\nTelekom.\n\nHe is working on automating virtually all aspects of running TeraStream, the\n\ndesign for Deutsche Telekom's next generation fixed network, using robust\nand\n\nfault tolerant software. He is active in the IETF as well as being a\n\nrepresenting member in OpenConfig. Previous to joining Deutsche Telekom,\n\nKristian was the IP & opto network architect for Tele2's international\nbackbone\n\nnetwork.\n\n\n\"[BB-8 in action](https://unsplash.com/photos/C8VWyZhcIIU) by [Joseph\nChan](https://unsplash.com/@yulokchan) on Unsplash\n\n{: .note}\n","engineering",[23,24,25],"CI/CD","user stories","git",{"slug":27,"featured":6,"template":28},"automating-boring-git-operations-gitlab-ci","BlogPost","content:en-us:blog:automating-boring-git-operations-gitlab-ci.yml","yaml","Automating Boring Git Operations Gitlab Ci","content","en-us/blog/automating-boring-git-operations-gitlab-ci.yml","en-us/blog/automating-boring-git-operations-gitlab-ci","yml",{"_path":37,"_dir":38,"_draft":6,"_partial":6,"_locale":7,"data":39,"_id":460,"_type":30,"title":461,"_source":32,"_file":462,"_stem":463,"_extension":35},"/shared/en-us/main-navigation","en-us",{"logo":40,"freeTrial":45,"sales":50,"login":55,"items":60,"search":391,"minimal":422,"duo":441,"pricingDeployment":450},{"config":41},{"href":42,"dataGaName":43,"dataGaLocation":44},"/","gitlab logo","header",{"text":46,"config":47},"Get free trial",{"href":48,"dataGaName":49,"dataGaLocation":44},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":51,"config":52},"Talk to sales",{"href":53,"dataGaName":54,"dataGaLocation":44},"/sales/","sales",{"text":56,"config":57},"Sign in",{"href":58,"dataGaName":59,"dataGaLocation":44},"https://gitlab.com/users/sign_in/","sign in",[61,105,202,207,312,372],{"text":62,"config":63,"cards":65,"footer":88},"Platform",{"dataNavLevelOne":64},"platform",[66,72,80],{"title":62,"description":67,"link":68},"The most comprehensive AI-powered DevSecOps Platform",{"text":69,"config":70},"Explore our Platform",{"href":71,"dataGaName":64,"dataGaLocation":44},"/platform/",{"title":73,"description":74,"link":75},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":76,"config":77},"Meet GitLab Duo",{"href":78,"dataGaName":79,"dataGaLocation":44},"/gitlab-duo/","gitlab duo ai",{"title":81,"description":82,"link":83},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":84,"config":85},"Learn more",{"href":86,"dataGaName":87,"dataGaLocation":44},"/why-gitlab/","why gitlab",{"title":89,"items":90},"Get started with",[91,96,101],{"text":92,"config":93},"Platform Engineering",{"href":94,"dataGaName":95,"dataGaLocation":44},"/solutions/platform-engineering/","platform engineering",{"text":97,"config":98},"Developer Experience",{"href":99,"dataGaName":100,"dataGaLocation":44},"/developer-experience/","Developer experience",{"text":102,"config":103},"MLOps",{"href":104,"dataGaName":102,"dataGaLocation":44},"/topics/devops/the-role-of-ai-in-devops/",{"text":106,"left":107,"config":108,"link":110,"lists":114,"footer":184},"Product",true,{"dataNavLevelOne":109},"solutions",{"text":111,"config":112},"View all Solutions",{"href":113,"dataGaName":109,"dataGaLocation":44},"/solutions/",[115,139,163],{"title":116,"description":117,"link":118,"items":123},"Automation","CI/CD and automation to accelerate deployment",{"config":119},{"icon":120,"href":121,"dataGaName":122,"dataGaLocation":44},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[124,127,131,135],{"text":23,"config":125},{"href":126,"dataGaLocation":44,"dataGaName":23},"/solutions/continuous-integration/",{"text":128,"config":129},"AI-Assisted Development",{"href":78,"dataGaLocation":44,"dataGaName":130},"AI assisted development",{"text":132,"config":133},"Source Code Management",{"href":134,"dataGaLocation":44,"dataGaName":132},"/solutions/source-code-management/",{"text":136,"config":137},"Automated Software Delivery",{"href":121,"dataGaLocation":44,"dataGaName":138},"Automated software delivery",{"title":140,"description":141,"link":142,"items":147},"Security","Deliver code faster without compromising security",{"config":143},{"href":144,"dataGaName":145,"dataGaLocation":44,"icon":146},"/solutions/security-compliance/","security and compliance","ShieldCheckLight",[148,153,158],{"text":149,"config":150},"Application Security Testing",{"href":151,"dataGaName":152,"dataGaLocation":44},"/solutions/application-security-testing/","Application security testing",{"text":154,"config":155},"Software Supply Chain Security",{"href":156,"dataGaLocation":44,"dataGaName":157},"/solutions/supply-chain/","Software supply chain security",{"text":159,"config":160},"Software Compliance",{"href":161,"dataGaName":162,"dataGaLocation":44},"/solutions/software-compliance/","software compliance",{"title":164,"link":165,"items":170},"Measurement",{"config":166},{"icon":167,"href":168,"dataGaName":169,"dataGaLocation":44},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[171,175,179],{"text":172,"config":173},"Visibility & Measurement",{"href":168,"dataGaLocation":44,"dataGaName":174},"Visibility and Measurement",{"text":176,"config":177},"Value Stream Management",{"href":178,"dataGaLocation":44,"dataGaName":176},"/solutions/value-stream-management/",{"text":180,"config":181},"Analytics & Insights",{"href":182,"dataGaLocation":44,"dataGaName":183},"/solutions/analytics-and-insights/","Analytics and insights",{"title":185,"items":186},"GitLab for",[187,192,197],{"text":188,"config":189},"Enterprise",{"href":190,"dataGaLocation":44,"dataGaName":191},"/enterprise/","enterprise",{"text":193,"config":194},"Small Business",{"href":195,"dataGaLocation":44,"dataGaName":196},"/small-business/","small business",{"text":198,"config":199},"Public Sector",{"href":200,"dataGaLocation":44,"dataGaName":201},"/solutions/public-sector/","public sector",{"text":203,"config":204},"Pricing",{"href":205,"dataGaName":206,"dataGaLocation":44,"dataNavLevelOne":206},"/pricing/","pricing",{"text":208,"config":209,"link":211,"lists":215,"feature":299},"Resources",{"dataNavLevelOne":210},"resources",{"text":212,"config":213},"View all resources",{"href":214,"dataGaName":210,"dataGaLocation":44},"/resources/",[216,249,271],{"title":217,"items":218},"Getting started",[219,224,229,234,239,244],{"text":220,"config":221},"Install",{"href":222,"dataGaName":223,"dataGaLocation":44},"/install/","install",{"text":225,"config":226},"Quick start guides",{"href":227,"dataGaName":228,"dataGaLocation":44},"/get-started/","quick setup checklists",{"text":230,"config":231},"Learn",{"href":232,"dataGaLocation":44,"dataGaName":233},"https://university.gitlab.com/","learn",{"text":235,"config":236},"Product documentation",{"href":237,"dataGaName":238,"dataGaLocation":44},"https://docs.gitlab.com/","product documentation",{"text":240,"config":241},"Best practice videos",{"href":242,"dataGaName":243,"dataGaLocation":44},"/getting-started-videos/","best practice videos",{"text":245,"config":246},"Integrations",{"href":247,"dataGaName":248,"dataGaLocation":44},"/integrations/","integrations",{"title":250,"items":251},"Discover",[252,257,261,266],{"text":253,"config":254},"Customer success stories",{"href":255,"dataGaName":256,"dataGaLocation":44},"/customers/","customer success stories",{"text":258,"config":259},"Blog",{"href":260,"dataGaName":5,"dataGaLocation":44},"/blog/",{"text":262,"config":263},"Remote",{"href":264,"dataGaName":265,"dataGaLocation":44},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":267,"config":268},"TeamOps",{"href":269,"dataGaName":270,"dataGaLocation":44},"/teamops/","teamops",{"title":272,"items":273},"Connect",[274,279,284,289,294],{"text":275,"config":276},"GitLab Services",{"href":277,"dataGaName":278,"dataGaLocation":44},"/services/","services",{"text":280,"config":281},"Community",{"href":282,"dataGaName":283,"dataGaLocation":44},"/community/","community",{"text":285,"config":286},"Forum",{"href":287,"dataGaName":288,"dataGaLocation":44},"https://forum.gitlab.com/","forum",{"text":290,"config":291},"Events",{"href":292,"dataGaName":293,"dataGaLocation":44},"/events/","events",{"text":295,"config":296},"Partners",{"href":297,"dataGaName":298,"dataGaLocation":44},"/partners/","partners",{"backgroundColor":300,"textColor":301,"text":302,"image":303,"link":307},"#2f2a6b","#fff","Insights for the future of software development",{"altText":304,"config":305},"the source promo card",{"src":306},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":308,"config":309},"Read the latest",{"href":310,"dataGaName":311,"dataGaLocation":44},"/the-source/","the source",{"text":313,"config":314,"lists":316},"Company",{"dataNavLevelOne":315},"company",[317],{"items":318},[319,324,330,332,337,342,347,352,357,362,367],{"text":320,"config":321},"About",{"href":322,"dataGaName":323,"dataGaLocation":44},"/company/","about",{"text":325,"config":326,"footerGa":329},"Jobs",{"href":327,"dataGaName":328,"dataGaLocation":44},"/jobs/","jobs",{"dataGaName":328},{"text":290,"config":331},{"href":292,"dataGaName":293,"dataGaLocation":44},{"text":333,"config":334},"Leadership",{"href":335,"dataGaName":336,"dataGaLocation":44},"/company/team/e-group/","leadership",{"text":338,"config":339},"Team",{"href":340,"dataGaName":341,"dataGaLocation":44},"/company/team/","team",{"text":343,"config":344},"Handbook",{"href":345,"dataGaName":346,"dataGaLocation":44},"https://handbook.gitlab.com/","handbook",{"text":348,"config":349},"Investor relations",{"href":350,"dataGaName":351,"dataGaLocation":44},"https://ir.gitlab.com/","investor relations",{"text":353,"config":354},"Trust Center",{"href":355,"dataGaName":356,"dataGaLocation":44},"/security/","trust center",{"text":358,"config":359},"AI Transparency Center",{"href":360,"dataGaName":361,"dataGaLocation":44},"/ai-transparency-center/","ai transparency center",{"text":363,"config":364},"Newsletter",{"href":365,"dataGaName":366,"dataGaLocation":44},"/company/contact/","newsletter",{"text":368,"config":369},"Press",{"href":370,"dataGaName":371,"dataGaLocation":44},"/press/","press",{"text":373,"config":374,"lists":375},"Contact us",{"dataNavLevelOne":315},[376],{"items":377},[378,381,386],{"text":51,"config":379},{"href":53,"dataGaName":380,"dataGaLocation":44},"talk to sales",{"text":382,"config":383},"Get help",{"href":384,"dataGaName":385,"dataGaLocation":44},"/support/","get help",{"text":387,"config":388},"Customer portal",{"href":389,"dataGaName":390,"dataGaLocation":44},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":392,"login":393,"suggestions":400},"Close",{"text":394,"link":395},"To search repositories and projects, login to",{"text":396,"config":397},"gitlab.com",{"href":58,"dataGaName":398,"dataGaLocation":399},"search login","search",{"text":401,"default":402},"Suggestions",[403,405,409,411,415,419],{"text":73,"config":404},{"href":78,"dataGaName":73,"dataGaLocation":399},{"text":406,"config":407},"Code Suggestions (AI)",{"href":408,"dataGaName":406,"dataGaLocation":399},"/solutions/code-suggestions/",{"text":23,"config":410},{"href":126,"dataGaName":23,"dataGaLocation":399},{"text":412,"config":413},"GitLab on AWS",{"href":414,"dataGaName":412,"dataGaLocation":399},"/partners/technology-partners/aws/",{"text":416,"config":417},"GitLab on Google Cloud",{"href":418,"dataGaName":416,"dataGaLocation":399},"/partners/technology-partners/google-cloud-platform/",{"text":420,"config":421},"Why GitLab?",{"href":86,"dataGaName":420,"dataGaLocation":399},{"freeTrial":423,"mobileIcon":428,"desktopIcon":433,"secondaryButton":436},{"text":424,"config":425},"Start free trial",{"href":426,"dataGaName":49,"dataGaLocation":427},"https://gitlab.com/-/trials/new/","nav",{"altText":429,"config":430},"Gitlab Icon",{"src":431,"dataGaName":432,"dataGaLocation":427},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":429,"config":434},{"src":435,"dataGaName":432,"dataGaLocation":427},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":437,"config":438},"Get Started",{"href":439,"dataGaName":440,"dataGaLocation":427},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":442,"mobileIcon":446,"desktopIcon":448},{"text":443,"config":444},"Learn more about GitLab Duo",{"href":78,"dataGaName":445,"dataGaLocation":427},"gitlab duo",{"altText":429,"config":447},{"src":431,"dataGaName":432,"dataGaLocation":427},{"altText":429,"config":449},{"src":435,"dataGaName":432,"dataGaLocation":427},{"freeTrial":451,"mobileIcon":456,"desktopIcon":458},{"text":452,"config":453},"Back to pricing",{"href":205,"dataGaName":454,"dataGaLocation":427,"icon":455},"back to pricing","GoBack",{"altText":429,"config":457},{"src":431,"dataGaName":432,"dataGaLocation":427},{"altText":429,"config":459},{"src":435,"dataGaName":432,"dataGaLocation":427},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":465,"_dir":38,"_draft":6,"_partial":6,"_locale":7,"title":466,"button":467,"image":472,"config":476,"_id":478,"_type":30,"_source":32,"_file":479,"_stem":480,"_extension":35},"/shared/en-us/banner","is now in public beta!",{"text":468,"config":469},"Try the Beta",{"href":470,"dataGaName":471,"dataGaLocation":44},"/gitlab-duo/agent-platform/","duo banner",{"altText":473,"config":474},"GitLab Duo Agent Platform",{"src":475},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":477},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":482,"_dir":38,"_draft":6,"_partial":6,"_locale":7,"data":483,"_id":687,"_type":30,"title":688,"_source":32,"_file":689,"_stem":690,"_extension":35},"/shared/en-us/main-footer",{"text":484,"source":485,"edit":491,"contribute":496,"config":501,"items":506,"minimal":679},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":486,"config":487},"View page source",{"href":488,"dataGaName":489,"dataGaLocation":490},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":492,"config":493},"Edit this page",{"href":494,"dataGaName":495,"dataGaLocation":490},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":497,"config":498},"Please contribute",{"href":499,"dataGaName":500,"dataGaLocation":490},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":502,"facebook":503,"youtube":504,"linkedin":505},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[507,530,586,615,649],{"title":62,"links":508,"subMenu":513},[509],{"text":510,"config":511},"DevSecOps platform",{"href":71,"dataGaName":512,"dataGaLocation":490},"devsecops platform",[514],{"title":203,"links":515},[516,520,525],{"text":517,"config":518},"View plans",{"href":205,"dataGaName":519,"dataGaLocation":490},"view plans",{"text":521,"config":522},"Why Premium?",{"href":523,"dataGaName":524,"dataGaLocation":490},"/pricing/premium/","why premium",{"text":526,"config":527},"Why Ultimate?",{"href":528,"dataGaName":529,"dataGaLocation":490},"/pricing/ultimate/","why ultimate",{"title":531,"links":532},"Solutions",[533,538,540,542,547,552,556,559,563,568,570,573,576,581],{"text":534,"config":535},"Digital transformation",{"href":536,"dataGaName":537,"dataGaLocation":490},"/topics/digital-transformation/","digital transformation",{"text":149,"config":539},{"href":151,"dataGaName":149,"dataGaLocation":490},{"text":138,"config":541},{"href":121,"dataGaName":122,"dataGaLocation":490},{"text":543,"config":544},"Agile development",{"href":545,"dataGaName":546,"dataGaLocation":490},"/solutions/agile-delivery/","agile delivery",{"text":548,"config":549},"Cloud transformation",{"href":550,"dataGaName":551,"dataGaLocation":490},"/topics/cloud-native/","cloud transformation",{"text":553,"config":554},"SCM",{"href":134,"dataGaName":555,"dataGaLocation":490},"source code management",{"text":23,"config":557},{"href":126,"dataGaName":558,"dataGaLocation":490},"continuous integration & delivery",{"text":560,"config":561},"Value stream management",{"href":178,"dataGaName":562,"dataGaLocation":490},"value stream management",{"text":564,"config":565},"GitOps",{"href":566,"dataGaName":567,"dataGaLocation":490},"/solutions/gitops/","gitops",{"text":188,"config":569},{"href":190,"dataGaName":191,"dataGaLocation":490},{"text":571,"config":572},"Small business",{"href":195,"dataGaName":196,"dataGaLocation":490},{"text":574,"config":575},"Public sector",{"href":200,"dataGaName":201,"dataGaLocation":490},{"text":577,"config":578},"Education",{"href":579,"dataGaName":580,"dataGaLocation":490},"/solutions/education/","education",{"text":582,"config":583},"Financial services",{"href":584,"dataGaName":585,"dataGaLocation":490},"/solutions/finance/","financial services",{"title":208,"links":587},[588,590,592,594,597,599,601,603,605,607,609,611,613],{"text":220,"config":589},{"href":222,"dataGaName":223,"dataGaLocation":490},{"text":225,"config":591},{"href":227,"dataGaName":228,"dataGaLocation":490},{"text":230,"config":593},{"href":232,"dataGaName":233,"dataGaLocation":490},{"text":235,"config":595},{"href":237,"dataGaName":596,"dataGaLocation":490},"docs",{"text":258,"config":598},{"href":260,"dataGaName":5,"dataGaLocation":490},{"text":253,"config":600},{"href":255,"dataGaName":256,"dataGaLocation":490},{"text":262,"config":602},{"href":264,"dataGaName":265,"dataGaLocation":490},{"text":275,"config":604},{"href":277,"dataGaName":278,"dataGaLocation":490},{"text":267,"config":606},{"href":269,"dataGaName":270,"dataGaLocation":490},{"text":280,"config":608},{"href":282,"dataGaName":283,"dataGaLocation":490},{"text":285,"config":610},{"href":287,"dataGaName":288,"dataGaLocation":490},{"text":290,"config":612},{"href":292,"dataGaName":293,"dataGaLocation":490},{"text":295,"config":614},{"href":297,"dataGaName":298,"dataGaLocation":490},{"title":313,"links":616},[617,619,621,623,625,627,629,633,638,640,642,644],{"text":320,"config":618},{"href":322,"dataGaName":315,"dataGaLocation":490},{"text":325,"config":620},{"href":327,"dataGaName":328,"dataGaLocation":490},{"text":333,"config":622},{"href":335,"dataGaName":336,"dataGaLocation":490},{"text":338,"config":624},{"href":340,"dataGaName":341,"dataGaLocation":490},{"text":343,"config":626},{"href":345,"dataGaName":346,"dataGaLocation":490},{"text":348,"config":628},{"href":350,"dataGaName":351,"dataGaLocation":490},{"text":630,"config":631},"Sustainability",{"href":632,"dataGaName":630,"dataGaLocation":490},"/sustainability/",{"text":634,"config":635},"Diversity, inclusion and belonging (DIB)",{"href":636,"dataGaName":637,"dataGaLocation":490},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":353,"config":639},{"href":355,"dataGaName":356,"dataGaLocation":490},{"text":363,"config":641},{"href":365,"dataGaName":366,"dataGaLocation":490},{"text":368,"config":643},{"href":370,"dataGaName":371,"dataGaLocation":490},{"text":645,"config":646},"Modern Slavery Transparency Statement",{"href":647,"dataGaName":648,"dataGaLocation":490},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":650,"links":651},"Contact Us",[652,655,657,659,664,669,674],{"text":653,"config":654},"Contact an expert",{"href":53,"dataGaName":54,"dataGaLocation":490},{"text":382,"config":656},{"href":384,"dataGaName":385,"dataGaLocation":490},{"text":387,"config":658},{"href":389,"dataGaName":390,"dataGaLocation":490},{"text":660,"config":661},"Status",{"href":662,"dataGaName":663,"dataGaLocation":490},"https://status.gitlab.com/","status",{"text":665,"config":666},"Terms of use",{"href":667,"dataGaName":668,"dataGaLocation":490},"/terms/","terms of use",{"text":670,"config":671},"Privacy statement",{"href":672,"dataGaName":673,"dataGaLocation":490},"/privacy/","privacy statement",{"text":675,"config":676},"Cookie preferences",{"dataGaName":677,"dataGaLocation":490,"id":678,"isOneTrustButton":107},"cookie preferences","ot-sdk-btn",{"items":680},[681,683,685],{"text":665,"config":682},{"href":667,"dataGaName":668,"dataGaLocation":490},{"text":670,"config":684},{"href":672,"dataGaName":673,"dataGaLocation":490},{"text":675,"config":686},{"dataGaName":677,"dataGaLocation":490,"id":678,"isOneTrustButton":107},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",[692],{"_path":693,"_dir":694,"_draft":6,"_partial":6,"_locale":7,"content":695,"config":699,"_id":701,"_type":30,"title":18,"_source":32,"_file":702,"_stem":703,"_extension":35},"/en-us/blog/authors/kristian-larsson","authors",{"name":18,"config":696},{"headshot":697,"ctfId":698},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659488/Blog/Author%20Headshots/gitlab-logo-extra-whitespace.png","Kristian-Larsson",{"template":700},"BlogAuthor","content:en-us:blog:authors:kristian-larsson.yml","en-us/blog/authors/kristian-larsson.yml","en-us/blog/authors/kristian-larsson",{"_path":705,"_dir":38,"_draft":6,"_partial":6,"_locale":7,"header":706,"eyebrow":707,"blurb":708,"button":709,"secondaryButton":713,"_id":715,"_type":30,"title":716,"_source":32,"_file":717,"_stem":718,"_extension":35},"/shared/en-us/next-steps","Start shipping better software faster","50%+ of the Fortune 100 trust GitLab","See what your team can do with the intelligent\n\n\nDevSecOps platform.\n",{"text":46,"config":710},{"href":711,"dataGaName":49,"dataGaLocation":712},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":51,"config":714},{"href":53,"dataGaName":54,"dataGaLocation":712},"content:shared:en-us:next-steps.yml","Next Steps","shared/en-us/next-steps.yml","shared/en-us/next-steps",1758326247737]