<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Robin Shen</title>
    <description>The latest articles on DEV Community by Robin Shen (@robinshine).</description>
    <link>https://dev.to/robinshine</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F350568%2F355fc55c-8ba8-4e81-a564-e8c1ef365b2e.jpeg</url>
      <title>DEV Community: Robin Shen</title>
      <link>https://dev.to/robinshine</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/robinshine"/>
    <language>en</language>
    <item>
      <title>When an Ordinary Workflow Needs a Hack, Ask AI to Step Back</title>
      <dc:creator>Robin Shen</dc:creator>
      <pubDate>Tue, 14 Jul 2026 01:55:51 +0000</pubDate>
      <link>https://dev.to/robinshine/when-an-ordinary-workflow-needs-a-hack-ask-ai-to-step-back-3ln7</link>
      <guid>https://dev.to/robinshine/when-an-ordinary-workflow-needs-a-hack-ask-ai-to-step-back-3ln7</guid>
      <description>&lt;p&gt;Coding agents are remarkably good at finding the next plausible fix. If a cookie is not removed, they adjust the cookie handling. If two browsers behave differently, they investigate browser behavior. If a result depends on timing, they add a delay.&lt;/p&gt;

&lt;p&gt;Every step can be reasonable. The danger is that a long chain of reasonable patches can lead to a solution that works without ever addressing the real problem.&lt;/p&gt;

&lt;p&gt;We ran into exactly this while migrating &lt;a href="https://onedev.io" rel="noopener noreferrer"&gt;onedev.io&lt;/a&gt; from an older web framework to Next.js. Signing out—one of the most ordinary actions on any website—worked only occasionally. After several rounds of debugging, a short delay made it reliable.&lt;/p&gt;

&lt;p&gt;The delay solved the symptom. It also revealed that we were asking the wrong question.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Routine Feature Became Mysteriously Unreliable
&lt;/h2&gt;

&lt;p&gt;I should first explain my role in this story: I am completely unfamiliar with React and Next.js. I relied entirely on a coding agent to migrate the website and investigate the problem.&lt;/p&gt;

&lt;p&gt;I could describe what I saw, test changes, and decide whether a result felt acceptable. I could not tell the agent which framework feature to use or which part of the architecture to inspect. That is, after all, why I delegated the work.&lt;/p&gt;

&lt;p&gt;The visible problem was simple. Clicking &lt;strong&gt;Sign Out&lt;/strong&gt; returned me to the home page, but I could sometimes still open pages reserved for signed-in users—even after a hard refresh.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgamkc0n8nwmewd2jawdw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgamkc0n8nwmewd2jawdw.png" alt="The ordinary Sign Out action that exposed the deeper design problem" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The behavior was inconsistent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It sometimes worked in Safari but not Chrome.&lt;/li&gt;
&lt;li&gt;Clearing all cookies helped only temporarily.&lt;/li&gt;
&lt;li&gt;Opening browser developer tools made it more likely to work.&lt;/li&gt;
&lt;li&gt;Some fixes improved the success rate without making it reliable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These clues made the browser look suspicious. But the browser was only where the problem became visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reasonable Debugging Slowly Turned Into Patchwork
&lt;/h2&gt;

&lt;p&gt;The website sits behind several layers: the browser connects through Cloudflare, Cloudflare connects to nginx, and nginx forwards the request to the application. It was natural to suspect that one of those layers was confusing secure and non-secure cookies.&lt;/p&gt;

&lt;p&gt;We checked the public URL, forwarded connection information, cookie names, Cloudflare features, and old browser data. We also tried making the sign-out code more explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete every possible form of the session cookie.&lt;/li&gt;
&lt;li&gt;Create a dedicated sign-out endpoint.&lt;/li&gt;
&lt;li&gt;Exclude that endpoint from global authentication handling.&lt;/li&gt;
&lt;li&gt;Change click handling in case navigation started too early.&lt;/li&gt;
&lt;li&gt;Wait for the server response before returning to the home page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each attempt was based on a real observation. None was foolish. But the growing list was itself an important observation: a routine operation was accumulating special handling.&lt;/p&gt;

&lt;p&gt;Eventually, this worked reliably:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Signing out…&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application showed a brief “Signing out…” page, waited 250 milliseconds, and then went home.&lt;/p&gt;

&lt;p&gt;Problem solved?&lt;/p&gt;

&lt;p&gt;Technically, yes. The website behaved correctly. But a normal sign-out should not depend on an arbitrary pause. Why 250 milliseconds? Would 100 be enough? Would it still work on a slower connection? Why should opening developer tools change anything?&lt;/p&gt;

&lt;p&gt;The delay was not a satisfying answer. It was evidence that two things were happening in the wrong order.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Most Useful Prompt Was Not Technical
&lt;/h2&gt;

&lt;p&gt;At this point, I did not give the agent a detailed React or Next.js diagnosis. I could not have done so. I simply challenged the result:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a very common workflow. It should not require a timing hack. Please revisit the relevant code, check whether the implementation follows common conventions and best practices, find the root cause, and remove the workaround.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That prompt changed the direction of the investigation.&lt;/p&gt;

&lt;p&gt;Until then, the work had focused on making the failing sign-out action more robust. Now the agent stepped back and reviewed the whole authentication workflow: where a session was created, where it was checked, where it could be refreshed, and where it was removed.&lt;/p&gt;

&lt;p&gt;This is an important distinction in vibe coding. You do not need to know the names of all the framework components. You need to recognize when the shape of a solution feels wrong and ask the agent to widen its view.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem Was Outside the Sign-Out Code
&lt;/h2&gt;

&lt;p&gt;The website had been migrated from an older stack. My main instruction during that migration was to preserve existing behavior while changing the framework. That was a sensible requirement, but it encouraged the new implementation to carry forward some old, request-wide behavior.&lt;/p&gt;

&lt;p&gt;In the migrated site, nearly every page request passed through a global authentication wrapper. In simplified form, it looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Prepare information used by the migrated website.&lt;/span&gt;
  &lt;span class="c1"&gt;// Authentication also runs for nearly every request.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The wrapper was convenient because it made authentication information available everywhere. But the application already checked access again around the pages that actually required a signed-in user.&lt;/p&gt;

&lt;p&gt;In plain language, two parts of the application had taken responsibility for the same thing.&lt;/p&gt;

&lt;p&gt;That duplication caused a race:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The sign-out request told the browser to remove the session.&lt;/li&gt;
&lt;li&gt;A nearby page request passed through the global wrapper and refreshed that same session.&lt;/li&gt;
&lt;li&gt;Whichever response the browser processed last decided whether the user appeared signed in or signed out.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This explained all the strange behavior. Different browsers, developer tools, and the 250-millisecond delay changed the timing. They changed which response won, but they did not create the underlying problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Structural Fix Made the Workaround Disappear
&lt;/h2&gt;

&lt;p&gt;The fix was to give each part of the application one clear job.&lt;/p&gt;

&lt;p&gt;The global request handler kept only the small piece of migrated page information it genuinely needed to provide:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-pathname&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pages that require a signed-in user continued to protect themselves. The authentication library returned to handling sign-out through its normal built-in function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;signOut&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;redirectTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we removed the custom endpoint, manual cookie deletion, special click handling, and 250-millisecond delay.&lt;/p&gt;

&lt;p&gt;Sign-out worked reliably in both Chrome and Safari.&lt;/p&gt;

&lt;p&gt;The final code was not only more correct; it was much simpler than the patched version. That is often a strong sign that the investigation has reached the real cause.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Teaches Us About Vibe Coding
&lt;/h2&gt;

&lt;p&gt;Vibe coding does not mean accepting whatever code happens to work. Nor does it require the user to become an expert in every framework the agent touches.&lt;/p&gt;

&lt;p&gt;The user brings something different but equally important: judgment about the expected shape of the result.&lt;/p&gt;

&lt;p&gt;You may not know how session cookies work, but you know that signing out is routine. You may not know the framework’s routing system, but you know that opening developer tools should not fix production behavior. You may not know which module owns a task, but you can see when a simple feature has collected delays, retries, duplicated logic, and browser-specific branches.&lt;/p&gt;

&lt;p&gt;Those are moments to stop asking for another local fix.&lt;/p&gt;

&lt;p&gt;A useful, technology-neutral prompt is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is a common workflow, but the solution feels like a hack.
Please step back and review the whole workflow. Check the relevant code
against the framework's common conventions and best practices, find the
root cause, simplify the design, and remove the workaround.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what this prompt does not contain. It does not list framework APIs. It does not guess at the root cause. It does not tell the agent how to implement the fix. The user identifies the mismatch between an ordinary requirement and an extraordinary solution; the agent supplies the technical investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Patches as Evidence, Not Architecture
&lt;/h2&gt;

&lt;p&gt;The 250-millisecond delay was not wasted work. It proved that timing mattered and helped narrow the investigation. Temporary patches are often useful experiments.&lt;/p&gt;

&lt;p&gt;The mistake would have been leaving that experiment in place and calling the task finished.&lt;/p&gt;

&lt;p&gt;When a workaround makes a bug disappear, ask what the workaround changed. A delay changes ordering. A retry may hide unreliable state. Manual cleanup may compensate for two components owning the same responsibility. A browser-specific branch may hide a problem that belongs on the server.&lt;/p&gt;

&lt;p&gt;The workaround often contains a clue about the root cause. Treat it as evidence, then remove it after the design is corrected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the Simple Path Simple Again
&lt;/h2&gt;

&lt;p&gt;The most valuable contribution I made to this debugging session was not technical. I did not recognize the global authentication wrapper as the problem, and I did not propose the final architecture. I simply refused to believe that a routine workflow should need an arbitrary delay.&lt;/p&gt;

&lt;p&gt;That was enough to redirect the coding agent from patching the symptom to reviewing the system.&lt;/p&gt;

&lt;p&gt;So when vibe coding produces a solution that works but feels unnatural, do not assume that your lack of framework knowledge means you must accept it. Ask the agent to step back. Ask whether the implementation follows the framework’s normal path. Ask it to find duplicated responsibility, remove accidental complexity, and make the common case simple again.&lt;/p&gt;

&lt;p&gt;This lesson came from &lt;a href="https://onedev.io" rel="noopener noreferrer"&gt;onedev.io&lt;/a&gt;, where coding agents are becoming part of our issue, pull request, workspace, and CI/CD workflows. The principle applies far beyond authentication: working code is an important milestone, but when an ordinary feature needs an extraordinary workaround, it should not be the finish line.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>TOD - command line tool to run OneDev CI/CD jobs against local changes</title>
      <dc:creator>Robin Shen</dc:creator>
      <pubDate>Tue, 27 Feb 2024 07:30:11 +0000</pubDate>
      <link>https://dev.to/robinshine/tod-command-line-tool-to-run-onedev-cicd-jobs-against-local-changes-1dm2</link>
      <guid>https://dev.to/robinshine/tod-command-line-tool-to-run-onedev-cicd-jobs-against-local-changes-1dm2</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/theonedev/onedev"&gt;OneDev 10.2&lt;/a&gt; comes with TOD (&lt;strong&gt;T&lt;/strong&gt;he*&lt;em&gt;O&lt;/em&gt;&lt;em&gt;ne&lt;/em&gt;&lt;em&gt;D&lt;/em&gt;*ev), a command line tool to test/debug CI/CD jobs against local changes to avoid the modify/commit/push/run/check cycles.&lt;/p&gt;

&lt;p&gt;Under the hood, tod stashes local changes to a commit and pushes to server under a temporal ref to run specified job. Log of running job is streamed back to tod so that you can check job progress and status without leaving terminal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzqlqxfrq2e0x2mv65fr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzqlqxfrq2e0x2mv65fr.gif" alt="Image description" width="1279" height="810"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since job runs on server, it does not have many quirks and limitations of other tools (gitlab-runner exec or nektos/act for instance), such as requiring you to set up job environments, unable to handle job dependencies, etc. And you can still get fast feedback via shallow clone and cache.&lt;/p&gt;

&lt;p&gt;For details, please check &lt;a href="https://github.com/theonedev/tod"&gt;tod project&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Open Source DevOps Server to Get Most Of Your .NET Projects</title>
      <dc:creator>Robin Shen</dc:creator>
      <pubDate>Sat, 09 Sep 2023 03:51:35 +0000</pubDate>
      <link>https://dev.to/robinshine/open-source-devops-server-to-get-most-of-your-net-projects-27e5</link>
      <guid>https://dev.to/robinshine/open-source-devops-server-to-get-most-of-your-net-projects-27e5</guid>
      <description>&lt;p&gt;&lt;a href="https://onedev.io"&gt;OneDev&lt;/a&gt; is an open source git hosting and CI/CD server. Unlike traditional code hosting platforms, it parses C# code to enable symbol search and navigation, both in source view and diff view:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xaXwgsXw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4f2708q6a4jyi6vdgsl1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xaXwgsXw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4f2708q6a4jyi6vdgsl1.gif" alt="Image description" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The latest version also adds the ability to suggest CI/CD job templates for .net projects, as well as analyzing TRX formatted unit test report, coverlet coverage report and roslynator code analysis report to annotate source and pull request diff to facilitate code review.&lt;/p&gt;

&lt;p&gt;Let’s test with a real project to demonstrate it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First start up OneDev server with a one-liner on a Linux machine with docker installed:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -it - rm -v $(pwd)/onedev:/opt/onedev -v /var/run/docker.sock:/var/run/docker.sock -p 6610:6610 1dev/server:9.1.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Access http:localhost:6610 to set up the server, and then import a repository like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Wk5bwArf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ko8knndtuhgj12ugw4ig.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Wk5bwArf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ko8knndtuhgj12ugw4ig.png" alt="Image description" width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Specify url as &lt;em&gt;https:github.com/bitwarden/server&lt;/em&gt;, and proceed with import with all others remain as default. After a while, we will see two projects get imported, respectively &lt;em&gt;bitwarden&lt;/em&gt;, and &lt;em&gt;bitwarden/server&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Navigate to project bitwarden/server and click the link &lt;em&gt;adding .onedev-buildspec.yml&lt;/em&gt; to set up CI/CD like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uK6YTYKJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i5tqp6v7fh94veg2zkgx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uK6YTYKJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i5tqp6v7fh94veg2zkgx.gif" alt="Image description" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OneDev under the hood checks if the project root contains dotnet solution file, and suggest a dotnet ci job if it exists. You may further tune the job to meet your requirement. For this demo project, we simply commit the change without any modification and the CI job will be running.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We can open the running build, and check its log like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hKCcrXkS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g836rc4en4hsvp18yrai.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hKCcrXkS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g836rc4en4hsvp18yrai.gif" alt="Image description" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The job takes some time. After it finishes, we will see three new tabs summarizing unit tests, code coverages, and code problems:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wwFRotg1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1k5hjft958momq2l93cn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wwFRotg1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1k5hjft958momq2l93cn.gif" alt="Image description" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the reports, you may drill down problems to source view directly&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When open source files in OneDev, coverage info and code problems will be displayed, and you can even comment the code problems to discuss with author:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4eppTJH6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y0crbazmlknvag4vkurz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4eppTJH6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y0crbazmlknvag4vkurz.gif" alt="Image description" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This also works for diff view in pull request, and is especially useful when review the change. That’s all. Thanks for watching.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>dotnet</category>
    </item>
    <item>
      <title>Code Review with OneDev</title>
      <dc:creator>Robin Shen</dc:creator>
      <pubDate>Tue, 28 Jun 2022 04:52:16 +0000</pubDate>
      <link>https://dev.to/robinshine/repository-mirror-with-onedev-8p8</link>
      <guid>https://dev.to/robinshine/repository-mirror-with-onedev-8p8</guid>
      <description>&lt;p&gt;&lt;a href="https://onedev.io"&gt;OneDev&lt;/a&gt; understands importance of code review, and provides many facilities to make this process smooth and efficient. This article introduces how to perform code review in OneDev with these facilities.&lt;/p&gt;

&lt;h1&gt;
  
  
  Review Rule Definition
&lt;/h1&gt;

&lt;p&gt;Review rule specifies who should review a certain change, and how many approvals should get to accept the change into a branch. Review rule is defined in branch protection settings like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6Yl9SiQt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mu3t34bbx2v6t82wum6t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6Yl9SiQt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mu3t34bbx2v6t82wum6t.png" alt="Image description" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example we define rules for changes to be accepted into main branch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The change must pass the CI build&lt;/li&gt;
&lt;li&gt;Changes touching models must get at least one approval from core team&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With this rule specified, one will not be able to push changes touching models to main branch directly even with repository write permission. Instead a pull request must be submitted to go through the review process. &lt;/p&gt;

&lt;h1&gt;
  
  
  Reviewer Auto-Suggestion
&lt;/h1&gt;

&lt;p&gt;When reviewers is specified as a group, OneDev will suggest the most appropriate members to review the change, based on commit history of touched files in the change. If for some reason, suggested reviewer can not review the change, you may simply remove and OneDev will pick up other appropriate reviewers. And you can add other reviewers as necessary even if review rule does not require it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7aDEBTPh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e16dia8fvjc7j4kdtlnv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7aDEBTPh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e16dia8fvjc7j4kdtlnv.png" alt="Image description" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Pull Request Build to Verify&amp;nbsp;Changes
&lt;/h1&gt;

&lt;p&gt;In above screenshot, the pull request also needs to be verified by CI build. We need to disable branch protection rule temporarily to edit build spec of main branch directly to add a pull request trigger for CI job like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hKOzO6PW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3pjzy07b6opz8yifvbjr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hKOzO6PW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3pjzy07b6opz8yifvbjr.png" alt="Image description" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After committing change, do not forget to re-enable the branch protection setting. Check the pull request again, a CI job will be running to verify merged commit of pull request head and target branch head.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mo6c4gV_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wekt8uqlpq5qadsqm3ri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mo6c4gV_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wekt8uqlpq5qadsqm3ri.png" alt="Image description" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Select Changes to&amp;nbsp;Review
&lt;/h1&gt;

&lt;p&gt;Code review can be performed from file changes tab. File changes can be shown commit by commit, for multiple commits, or since last review (last time approve or request for changes).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XRrjvGdv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a27dyk0yzqo3ekggj8fu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XRrjvGdv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a27dyk0yzqo3ekggj8fu.png" alt="Image description" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Further, files can be filtered to only show interested changes. For instance, below screenshot shows all changed java files under model package:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--09A7vvII--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qmw3mok6odulds4da3mw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--09A7vvII--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qmw3mok6odulds4da3mw.png" alt="Image description" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Jump to Symbol Definition
&lt;/h1&gt;

&lt;p&gt;While reviewing the change, one can jump to symbol definitions, or show all occurrences of the symbol in current commit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MzT21iZz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tnio7cv090y6cuzjn2bq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MzT21iZz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tnio7cv090y6cuzjn2bq.gif" alt="Image description" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Test Coverage and Code Problems
&lt;/h1&gt;

&lt;p&gt;OneDev can analyze test coverage and code analysis report generated by CI job to help code review, for instance:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5BOkApQV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nrbk0x6h4atdd2rl2s0j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5BOkApQV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nrbk0x6h4atdd2rl2s0j.png" alt="Image description" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For an example set up of diff annotation, please check &lt;a href="https://robinshen.medium.com/annotate-source-with-jest-eslint-information-in-onedev-c622641caa45"&gt;https://robinshen.medium.com/annotate-source-with-jest-eslint-information-in-onedev-c622641caa45&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Code Comment to Discuss Changes
&lt;/h1&gt;

&lt;p&gt;To raise concerns or suggest changes for a code snippet, just select with mouse to add a code comment:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lfA8DTBZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gyub6neurg0slbn6nbdo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lfA8DTBZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gyub6neurg0slbn6nbdo.gif" alt="Image description" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After reviewing the changes, reviewer can give opinion by approving or requesting for changes. The pull request can only be merged after getting approvals from all reviewers.&lt;/p&gt;

&lt;p&gt;Pull request submitter can check added code comments in code comments tab. For each suggested change, submitter can commit the change individually, or add to batch to commit together:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9JuojPiR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ft6vl2y4qjva6lxvvhfb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9JuojPiR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ft6vl2y4qjva6lxvvhfb.gif" alt="Image description" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Free-Style Review on Commit and File
&lt;/h1&gt;

&lt;p&gt;Besides pull request based review, you can open arbitrary file or commit to add comment to initiate free-style review. User changing selected code will automatically be mentioned and notified to participate in the discussion. Further, source file will be annotated with history comments to help code understanding even if file is modified or renamed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1riFsF3G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ppaydi1daqjl2nx8x0lo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1riFsF3G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ppaydi1daqjl2nx8x0lo.gif" alt="Image description" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this mode, you can still suggest changes. When apply suggested changes, user has to tell which branch to apply the change.&lt;/p&gt;

&lt;p&gt;That’s all. Hope these facilities helps with your next code review. Thanks for watching!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>development</category>
    </item>
    <item>
      <title>Performance Comparison of OneDev and GitLab</title>
      <dc:creator>Robin Shen</dc:creator>
      <pubDate>Wed, 01 Jun 2022 01:07:09 +0000</pubDate>
      <link>https://dev.to/robinshine/performance-comparison-of-onedev-and-gitlab-1597</link>
      <guid>https://dev.to/robinshine/performance-comparison-of-onedev-and-gitlab-1597</guid>
      <description>&lt;p&gt;TLDR;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git Push: OneDev is 40% faster than GitLab&lt;/li&gt;
&lt;li&gt;Git Clone: OneDev is 20% slower than GitLab&lt;/li&gt;
&lt;li&gt;Web UI: OneDev is 10x~30x faster than GitLab&lt;/li&gt;
&lt;li&gt;Memory: OneDev uses 70% less memory than GitLab&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For every feature built into &lt;a href="https://onedev.io"&gt;OneDev&lt;/a&gt;, I take performance seriously. It is very fast with moderate resource usage. In this article I compare it with GitLab to show some numbers.&lt;/p&gt;

&lt;h1&gt;
  
  
  Environment Setup
&lt;/h1&gt;

&lt;p&gt;OneDev and GitLab runs on same machine. When testing OneDev, GitLab is stopped, and vice versa.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hardware: AWS EC2 c5a.xlarge (4 core, 8G mem, GP2 SSD EBS storage)&lt;/li&gt;
&lt;li&gt;OS: Ubuntu 18.04&lt;/li&gt;
&lt;li&gt;OneDev: 7.3.2, installs using the zip file, running on openjdk 8&lt;/li&gt;
&lt;li&gt;GitLab: 14.10.2-ee, installs from official repository with apt-get&lt;/li&gt;
&lt;li&gt;Test repository: Kubernetes (100K+ commits)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since OneDev by default uses 50% of available memory, and this is too deluxe for a 8G machine, I modified its conf/wrapper.conf to change max memory to be 20%.&lt;/p&gt;

&lt;h1&gt;
  
  
  Repository Push/Clone
&lt;/h1&gt;

&lt;p&gt;Command used for a fresh push (repository is empty initially):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ time git push &amp;lt;repository url&amp;gt; master:master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And command used for clone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ time git clone &amp;lt;repository url&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tests done from another c5a.xlarge linux machine in same subnet. Result is as following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdrglh7syr6tuskxhcy9x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdrglh7syr6tuskxhcy9x.png" alt="Image description" width="448" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Git Push: OneDev is 40% faster than GitLab&lt;/li&gt;
&lt;li&gt;Git Clone: OneDev is 20% slower than GitLab&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is measured for full push/clone of a large repository. For daily increment push/pull , this is almost negligible.&lt;/p&gt;

&lt;p&gt;I did not measure concurrent push/clone, as both rely on native git to do the heavy lift, and the numbers should be on par.&lt;/p&gt;

&lt;h1&gt;
  
  
  Web UI
&lt;/h1&gt;

&lt;p&gt;Web UI test is performed with JMeter. The selenium web drive plugin is installed running chrome in headless mode to do end-to-end test. The idea is to visit a url, then wait for certain elements to appear. Taking GitLab pull request changes page for instance, I am using below groovy script for web drive sampler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import org.openqa.selenium.*
import org.openqa.selenium.support.ui.*
import java.time.Duration
def wait = new WebDriverWait(WDS.browser, 60)
WDS.sampleResult.sampleStart()
WDS.browser.get('http://testserver/root/kubernetes/-/merge_requests/1/diffs')
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector('.diff-content')))
WDS.sampleResult.sampleEnd()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I started four c5a.2xlarge EC2 machine (8 cores, 16G mem) in same subnet as test server, each machine running 8 browsers via JMeter, and start another c5a.xlarge machine to aggregate results. This effectively simulates 32 concurrent users, and can drive server CPU to nearly 100%. Note that running 32 browsers on a 32 core VM can not drive OneDev to use 100% CPU, due to browser resource contention on same machine.&lt;/p&gt;

&lt;p&gt;Six similar pages in OneDev and GitLab are selected for test, displaying same set of data. For each round of test, the page is warmed up for 1 minute, then run 10 minutes to get the performance metrics. Gravatar of both products are disabled to avoid out-of-server resource loading. GitLab auto devops is also disabled to save cpu cycles.&lt;/p&gt;

&lt;h3&gt;
  
  
  Repository Root Page
&lt;/h3&gt;

&lt;p&gt;Both products show root of the repository including the readme content:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8zs3ijn8bnuqffurwkw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8zs3ijn8bnuqffurwkw.png" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fniockgp2tmzrtkxlo9dn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fniockgp2tmzrtkxlo9dn.png" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Test result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4pnkxmu6q55f36tx0l5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4pnkxmu6q55f36tx0l5.png" alt="Image description" width="800" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that for OneDev test in this case, I have to launch five c5a.2xlarge machine to simulate 40 concurrent users to drive it to use nearly 100% cpu.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recent Commits Page
&lt;/h3&gt;

&lt;p&gt;Both products show recent commits of the repository. OneDev shows 50 commits initially, while GitLab shows 40 commits. OneDev shows commits as verified as it trust GitHub GPG signing key by default.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvlin1p5ho8xa8721yds8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvlin1p5ho8xa8721yds8.png" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31euq3cd8akrnhxcdttp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31euq3cd8akrnhxcdttp.png" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Test result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhj88v44n9i8msrj8k0fr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhj88v44n9i8msrj8k0fr.png" alt="Image description" width="800" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Commit Detail Page
&lt;/h3&gt;

&lt;p&gt;Both products show detail of same commit, including diff of all changed files&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Falhbzv29w8qb7hig8kt2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Falhbzv29w8qb7hig8kt2.png" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxh4yeqt9z67so3xlaba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxh4yeqt9z67so3xlaba.png" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Test result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F68cbug6b6l625lcdn58x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F68cbug6b6l625lcdn58x.png" alt="Image description" width="800" height="135"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  File Content Page
&lt;/h3&gt;

&lt;p&gt;Both products show content of same repository file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe7tdqbewg7qv8it5uqd3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe7tdqbewg7qv8it5uqd3.png" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5fxcwn2fqms5jxt9j6n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5fxcwn2fqms5jxt9j6n.png" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Test result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmia1wc0478pqaolp4fpy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmia1wc0478pqaolp4fpy.png" alt="Image description" width="800" height="137"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pull Request Commits Page
&lt;/h3&gt;

&lt;p&gt;Both products show commit list of same pull request. OneDev shows earliest commit first, while GitLab shows latest commit first&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7yqw146564duz7qq3esa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7yqw146564duz7qq3esa.png" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5sorlzb39sd9h0n1fw1r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5sorlzb39sd9h0n1fw1r.png" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Test result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fchf393h5c3fsfgxxqdkw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fchf393h5c3fsfgxxqdkw.png" alt="Image description" width="800" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pull Request Changes Page
&lt;/h3&gt;

&lt;p&gt;Both products show diffs of changed files of same pull request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz9yd9x6ktbr2469gwcq2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz9yd9x6ktbr2469gwcq2.png" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn17c1nunp8cm4xah23yh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn17c1nunp8cm4xah23yh.png" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Test result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcpd6asvzvfkw7cn9ssoq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcpd6asvzvfkw7cn9ssoq.png" alt="Image description" width="800" height="134"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For all tests above, OneDev consumes 2.2G mem, while GitLab consumes 4G~4.5G mem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;OneDev web UI performs orders of magnitude faster than GitLab, often 10x~30x faster, with much less memory usage. The UI performance difference is very obvious when navigating through different pages of both products: OneDev loads page instantly after warm up, while GitLab has noticeable delays loading pages all the time.&lt;/p&gt;

&lt;p&gt;Thanks for watching. If you would like to run the tests yourself, check the &lt;a href="https://code.onedev.io/projects/406"&gt;setup detail&lt;/a&gt; here.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>git</category>
    </item>
    <item>
      <title>OneDev 7.1</title>
      <dc:creator>Robin Shen</dc:creator>
      <pubDate>Fri, 06 May 2022 03:24:56 +0000</pubDate>
      <link>https://dev.to/robinshine/onedev-71-2eli</link>
      <guid>https://dev.to/robinshine/onedev-71-2eli</guid>
      <description>&lt;p&gt;&lt;a href="https://onedev.io"&gt;OneDev&lt;/a&gt; is a self-hosted git service with built-in CI/CD and &lt;br&gt;
Kanban. The 7.1 release comes with below major improvements:&lt;/p&gt;

&lt;h3&gt;
  
  
  Command palette to access features quickly
&lt;/h3&gt;

&lt;p&gt;Press cmd/ctrl-k to bring up command palette from anywhere. Search everything and &lt;br&gt;
jump to it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TCi9oDDt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2um71c2dit5auwo95ecf.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TCi9oDDt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2um71c2dit5auwo95ecf.gif" alt="Image description" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy project navigation via tree breadbrumb
&lt;/h3&gt;

&lt;p&gt;Project navigation is much easier now with tree view in project list and breadcrumb&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ee_v6L6y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xp7957jffioog0cmn45b.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ee_v6L6y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xp7957jffioog0cmn45b.gif" alt="Image description" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Two-way repository sync
&lt;/h3&gt;

&lt;p&gt;Push to remote repository upon changes, or pull from remote repository upon remote change. Check &lt;a href="https://medium.com/p/acf8b85fc69e"&gt;this tutorial&lt;/a&gt; for details&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Klz6ptNR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gcu1lhqff3y43a2oanc3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Klz6ptNR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gcu1lhqff3y43a2oanc3.png" alt="Image description" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Display similar issues when create new issue
&lt;/h3&gt;

&lt;p&gt;Similar issues will be displayed in real-time when typing title of new issue, which is very convenient to prevent duplication issues being filed&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--w0bx_wIf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lw2wvmwoy4brwy0f0ab8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w0bx_wIf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lw2wvmwoy4brwy0f0ab8.gif" alt="Image description" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Full-text search with stop word filtering and word stemming
&lt;/h3&gt;

&lt;p&gt;When do fuzzy search against issues and pull requests, stop words are filtered (for major languages), and word stemming will be performed (for English currently)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5jto70A0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mexdbsh4xw76zw9607i8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5jto70A0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mexdbsh4xw76zw9607i8.gif" alt="Image description" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Able to change target branch of existing pull request
&lt;/h3&gt;

&lt;p&gt;Occasionally we need to change target branch of existing pull request without losing discussion context&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OE5Ph4Kr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4rtlww5sz2kg630zbfqh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OE5Ph4Kr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4rtlww5sz2kg630zbfqh.gif" alt="Image description" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>git</category>
    </item>
    <item>
      <title>Repository Mirror with OneDev</title>
      <dc:creator>Robin Shen</dc:creator>
      <pubDate>Wed, 04 May 2022 07:27:50 +0000</pubDate>
      <link>https://dev.to/robinshine/repository-mirror-with-onedev-5046</link>
      <guid>https://dev.to/robinshine/repository-mirror-with-onedev-5046</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;It is often desirable to mirror repositories between OneDev and other hosting services. For instance you may want to push changes to a copy at GitHub for public access, or want to pull back any changes contributed by others at GitHub.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://onedev.io"&gt;OneDev&lt;/a&gt; 7.1 made this task trivial with the new repository sync steps. Let’s see how to achieve this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preparation
&lt;/h3&gt;

&lt;p&gt;First of all, start OneDev by running below command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker run -t --rm -v /var/run/docker.sock:/var/run/docker.sock -v $(pwd)/onedev:/opt/onedev -p 6610:6610 -p 6611:6611 1dev/server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point your browser to &lt;em&gt;localhost:6610&lt;/em&gt; to setup OneDev, and create a test project.&lt;/p&gt;

&lt;p&gt;Then create an empty repository at GitHub side to be used as mirror of our test repository, assume the url is: &lt;em&gt;&lt;a href="https://github.com/youraccount/test"&gt;https://github.com/youraccount/test&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Push Changes to Mirror
&lt;/h3&gt;

&lt;p&gt;Now we configure OneDev to push changes to GitHub mirror as long as there is a change in branch or tag:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Login to your GitHub account and generate a personal access token able to push to the mirror repository&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;At OneDev side, create a job secret in the test project and use access token above as its value:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tHeukxiy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s05g69x60sdob6kgwzqm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tHeukxiy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s05g69x60sdob6kgwzqm.png" alt="Image description" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Define build spec to add a job say &lt;em&gt;Push to GitHub&lt;/em&gt;, and add a step of type Repository &lt;em&gt;Sync / Push to Remote&lt;/em&gt;. For &lt;em&gt;password / access token&lt;/em&gt; field, choose the job token defined above:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---GKIiIvC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kgypo4gp5udi9z2m4yaf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---GKIiIvC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kgypo4gp5udi9z2m4yaf.png" alt="Image description" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add triggers to run job automatically when branch is updated or tag is created:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UtC1RP43--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tkq1hl0nopc1w81qxaup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UtC1RP43--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tkq1hl0nopc1w81qxaup.png" alt="Image description" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now save the build spec and the job will run automatically to push changes to GitHub mirror. Check at GitHub side, and you will see it contains newly created file &lt;em&gt;.onedev-buildspec.yml&lt;/em&gt;. Experiment with other changes such as creating new branch/tag to see the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pull Changes from Mirror
&lt;/h3&gt;

&lt;p&gt;Our GitHub mirror may accept pull requests from contributors, so let’s set up OneDev to pull changes from it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Continue to edit build spec above, add a job say &lt;em&gt;Pull from GitHub&lt;/em&gt;, and add a step of type &lt;em&gt;Repository Sync / Pull from Remote&lt;/em&gt; like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9J8jAFQ1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l67ziaithai8ypch2ffa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9J8jAFQ1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l67ziaithai8ypch2ffa.png" alt="Image description" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here we use the same job token defined above to access GitHub mirror (access token can be left empty if the mirror is publicly accessible).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add a job trigger of cron type to run job on scheduled time, for instance, on 1:00AM every night:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5p2RZUYT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ytan9e8yo6gaynhx7k0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5p2RZUYT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ytan9e8yo6gaynhx7k0.png" alt="Image description" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If your OneDev instance can be accessed publicly, you can configure GitHub to trigger this pull job upon mirror changes. This can be done by creating a webhook at GitHub side like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bMzj_ClZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/41zk3bdao6ud0mb2t7sn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bMzj_ClZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/41zk3bdao6ud0mb2t7sn.png" alt="Image description" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The payload url is defined as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://onedev.example.com/api/trigger-job?project=test&amp;amp;job=Pull%20from%20GitHub&amp;amp;branch=master&amp;amp;access-token=&amp;lt;OneDev access token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;em&gt;onedev.example.com&lt;/em&gt; is dns name accessing your OneDev instance. Job name is url encoded as it contains space. &lt;em&gt;&lt;/em&gt; should be replaced by access token of a OneDev user with permission to run the job like below::&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rkjcdnaopfq3oych4xea.png)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For production use, it is important to enable https protocol for payload url to avoid leaking of access token.&lt;/p&gt;

&lt;p&gt;Save the webhook and OneDev should be pulling from GitHub mirror whenever it is changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;We’ve completed setup of a two-way sync of OneDev repository and its GitHub mirror. Since it is just an ordinary CI job, we can subscribe certain build query to be notified of sync failures, check build log to investigate sync failures, or even configure job resource setting to control resource usage of multiple sync jobs, etc.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>git</category>
      <category>github</category>
    </item>
    <item>
      <title>Understanding OneDev Pipeline</title>
      <dc:creator>Robin Shen</dc:creator>
      <pubDate>Mon, 11 Apr 2022 09:57:41 +0000</pubDate>
      <link>https://dev.to/robinshine/understanding-onedev-pipeline-47el</link>
      <guid>https://dev.to/robinshine/understanding-onedev-pipeline-47el</guid>
      <description>&lt;p&gt;&lt;a href="https://onedev.io"&gt;OneDev&lt;/a&gt; introduced pipeline concept since 7.0. In short, a pipeline is an execution of job dependency graph. It can be created by running any job in the graph, either manually or automatically via job triggers.&lt;/p&gt;

&lt;h1&gt;
  
  
  Create a Pipeline
&lt;/h1&gt;

&lt;p&gt;Login to &lt;a href="https://code.onedev.io/login"&gt;OneDev demo site&lt;/a&gt; with account &lt;em&gt;demo/demo&lt;/em&gt;, switch to &lt;a href="https://code.onedev.io/projects/369/blob/master/.onedev-buildspec.yml"&gt;CI spec definition page&lt;/a&gt; of the demo project, you will see job definitions like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M7FQKwnK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v16vx1176yiflrzwtnm4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M7FQKwnK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v16vx1176yiflrzwtnm4.png" alt="Image description" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we define a &lt;em&gt;test&lt;/em&gt; job to compile and test our app, and then publish compiled app as artifacts so that it can be deployed later. The &lt;em&gt;deploy&lt;/em&gt; job retrieves published app via dependency to the &lt;em&gt;test&lt;/em&gt; job, and gets it deployed as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HICNdeSO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qdyqlmpcew0xv4tp1h9v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HICNdeSO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qdyqlmpcew0xv4tp1h9v.png" alt="Image description" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now run the &lt;em&gt;test&lt;/em&gt; job, you will be brought to the page showing generated build. A pipeline will also be created as the job dependency graph is executed as result of running the &lt;em&gt;test&lt;/em&gt; job. The pipeline tab shows the created pipeline current build is part of:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ezHsyxp5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjww98kyem72vaj24nu0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ezHsyxp5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjww98kyem72vaj24nu0.png" alt="Image description" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: pipeline tab is only visible to users with code read permission&lt;/p&gt;

&lt;h1&gt;
  
  
  Pipeline Characteristics
&lt;/h1&gt;

&lt;p&gt;Pipeline has two characteristics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Builds fired manually from pipeline view, or by other builds in the pipeline via job triggers, dependencies and post-build actions belong to same pipeline.&lt;/li&gt;
&lt;li&gt;For a particular pipeline, only one build will be generated from same job and parameter. If no parameter is defined for the job, it can only be triggered once then.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To demonstrate, run the &lt;em&gt;deploy&lt;/em&gt; job from above pipeline, from the build log you will see that it is actually deploying the app published by existing test build in that pipeline:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GVvnq0jf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qn0qhzgq937euzsu715j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GVvnq0jf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qn0qhzgq937euzsu715j.png" alt="Image description" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also pipeline tab of the deploy build shows that it belongs to same pipeline as test build above:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--heSTTtx6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1b0zp7s2nd2jcvf150jt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--heSTTtx6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1b0zp7s2nd2jcvf150jt.png" alt="Image description" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we run &lt;em&gt;deploy&lt;/em&gt; job again in this pipeline, OneDev will indicate that the build is already fired:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--g5GNceyL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0sa0j6cb8zrjsqns4xc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g5GNceyL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0sa0j6cb8zrjsqns4xc.png" alt="Image description" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However if you run &lt;em&gt;deploy&lt;/em&gt; job from outside of the pipeline, a new pipeline will be created and new builds for &lt;em&gt;test&lt;/em&gt; and &lt;em&gt;deploy&lt;/em&gt; job will be generated for the new pipeline. Again, let’s check this in action. Switch to &lt;a href="https://code.onedev.io/projects/369/commits"&gt;commits page&lt;/a&gt; to run &lt;em&gt;deploy&lt;/em&gt; job for some commit with existing test build:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gKYAQbxg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l20yl4e0mue3xuyb8ouv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gKYAQbxg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l20yl4e0mue3xuyb8ouv.png" alt="Image description" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A new deploy build will be generated, as well as a new pipeline. Switch to pipeline tab of the new build, you will see that new test build also gets generated in regardless of existing test builds for the commit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9Y0I4k5N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9hintq9nq1balwmeaqaj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9Y0I4k5N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9hintq9nq1balwmeaqaj.png" alt="Image description" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Pipeline Automation
&lt;/h1&gt;

&lt;p&gt;Pipeline can be created automatically if a job is set to fire upon certain event. For instance, in &lt;a href="https://code.onedev.io/projects/370/blob/master/.onedev-buildspec.yml"&gt;CI spec&lt;/a&gt; of this demo project, we set job &lt;em&gt;test&lt;/em&gt; to trigger automatically when there are new commits in master branch:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y0teM84d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3es07pzacn5j4usck0dz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y0teM84d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3es07pzacn5j4usck0dz.png" alt="Image description" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://code.onedev.io/projects/369/blob/master/app.txt?mode=edit"&gt;Make some changes to app.txt&lt;/a&gt; and save the file, you will see that &lt;em&gt;test&lt;/em&gt; job is running:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ez51O61E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q5o3oxgnhsxwbxsn09n5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ez51O61E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q5o3oxgnhsxwbxsn09n5.png" alt="Image description" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the running test build and you will be brought to build detail page, from which created pipeline can be accessed.&lt;/p&gt;

&lt;p&gt;Now assume you want to deploy the app automatically when &lt;em&gt;test&lt;/em&gt; job is successful. To do it, add a trigger to fire the &lt;em&gt;deploy&lt;/em&gt; job when dependency jobs finished like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ipGYVnlu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wqizc9n1wp3lbuna5tiu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ipGYVnlu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wqizc9n1wp3lbuna5tiu.png" alt="Image description" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To test it in action, &lt;a href="https://code.onedev.io/projects/370/blob/master/app.txt?mode=edit"&gt;make some changes to app.txt in this project&lt;/a&gt;, save the file, open created pipeline and wait to see &lt;em&gt;deploy&lt;/em&gt; job running automatically after test job is successful:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l2z71Jeg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjdlpsml7x9opa0oam16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l2z71Jeg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjdlpsml7x9opa0oam16.png" alt="Image description" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alternatively, you may also configure a branch update trigger for &lt;em&gt;deploy&lt;/em&gt; job. In that case, both &lt;em&gt;deploy&lt;/em&gt; job and &lt;em&gt;test&lt;/em&gt; job will be fired upon changing file, but &lt;em&gt;deploy&lt;/em&gt; job will wait for &lt;em&gt;test&lt;/em&gt; job to complete before running as shown in the pipeline:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xSJG2N8b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/idxigvzd7kda2n6sajh5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xSJG2N8b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/idxigvzd7kda2n6sajh5.png" alt="Image description" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you should get an idea how pipeline works in OneDev. Any questions, please post at our &lt;a href="https://code.onedev.io/projects/160/issues?query=%22State%22+is+%22Open%22"&gt;issue tracker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for watching!&lt;/p&gt;

</description>
      <category>devops</category>
    </item>
    <item>
      <title>Set Up OneDev Issue Labels</title>
      <dc:creator>Robin Shen</dc:creator>
      <pubDate>Sun, 10 Apr 2022 07:48:45 +0000</pubDate>
      <link>https://dev.to/robinshine/set-up-onedev-issue-labels-i1g</link>
      <guid>https://dev.to/robinshine/set-up-onedev-issue-labels-i1g</guid>
      <description>&lt;p&gt;&lt;a href="https://onedev.io"&gt;OneDev&lt;/a&gt; is designed to be flexible, and this is no different for issues. In this tutorial, I will show how to customize issue labels.&lt;/p&gt;

&lt;p&gt;OneDev issues by default do not have label field, as custom fields work much better, for instance, you may have &lt;em&gt;Type&lt;/em&gt; field with values &lt;em&gt;Feature/Bug/Task&lt;/em&gt;, or &lt;em&gt;Subsystem&lt;/em&gt; field with values &lt;em&gt;Client/Server/Api&lt;/em&gt;, etc. However if you do want to have labels, here is how to do it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Login as administrator, and switch to custom issue fields page&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Kvfzjnpn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1xm70hott022h7z3pcz3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Kvfzjnpn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1xm70hott022h7z3pcz3.png" alt="Image description" width="800" height="552"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a custom issue field like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ENhbFMFD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9hxlnyf7p4hzotpolpp4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ENhbFMFD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9hxlnyf7p4hzotpolpp4.png" alt="Image description" width="800" height="578"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Continue to add available choices to include all possible label values, for instance:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4pbyDG51--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mskf3u5s6ondr1kjuc7o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4pbyDG51--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mskf3u5s6ondr1kjuc7o.png" alt="Image description" width="800" height="578"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now create an issue and you will see that we can specify labels:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QZ9gfCgm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kw1tq556ao46re8uuiwd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QZ9gfCgm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kw1tq556ao46re8uuiwd.png" alt="Image description" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To specify labels for existing issues, batch edit them and choose Labels field to edit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nfNrUcY0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v242zg36dgaubedpch94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nfNrUcY0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v242zg36dgaubedpch94.png" alt="Image description" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tZr20NKu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ggocjai2dttax6a0gq7n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tZr20NKu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ggocjai2dttax6a0gq7n.png" alt="Image description" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;By default newly created field is not displayed in issue list or board. This can be changed by configuring display fields option in issue list and board respectively:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4ratwgKZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4915xetrbyswwskx51e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4ratwgKZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4915xetrbyswwskx51e.png" alt="Image description" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2r343IE9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4n5uuh4mfflhencdx6py.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2r343IE9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4n5uuh4mfflhencdx6py.png" alt="Image description" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s all. Thanks for watching!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>git</category>
    </item>
    <item>
      <title>OneDev 7 - self-hosted git service with CI/CD and Kanban</title>
      <dc:creator>Robin Shen</dc:creator>
      <pubDate>Mon, 04 Apr 2022 12:02:31 +0000</pubDate>
      <link>https://dev.to/robinshine/onedev-7-self-hosted-git-service-with-cicd-and-kanban-3ihp</link>
      <guid>https://dev.to/robinshine/onedev-7-self-hosted-git-service-with-cicd-and-kanban-3ihp</guid>
      <description>&lt;p&gt;The self-hosted open source git service &lt;a href="https://github.com/theonedev/onedev"&gt;OneDev&lt;/a&gt; reaches 7.0. Some feature highlights:&lt;/p&gt;

&lt;h2&gt;
  
  
  Show job dependencies as pipeline
&lt;/h2&gt;

&lt;p&gt;Visualize job dependencies as pipeline to get instant understanding of job relationships and their execution statuses.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SHADoMct--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fg9fuwwm1jn4vnkm3rf7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SHADoMct--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fg9fuwwm1jn4vnkm3rf7.gif" alt="Image description" width="880" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Two-factor authentication
&lt;/h2&gt;

&lt;p&gt;TOTP based two-factor authentication can be enabled by user. Site administrator can also enforce 2FA for all users or certain groups&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bgqpfs66--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o59eygu6pcps7kt8rqnp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bgqpfs66--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o59eygu6pcps7kt8rqnp.png" alt="Image description" width="880" height="644"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Commit/Tag GPG signature verification and generation
&lt;/h2&gt;

&lt;p&gt;Commits and tags with GPG signature can be verified. Branch/tag protection rule can be set up to enforce signed commits/tags. OneDev can use its own signing key to sign commits/tags generated by itself (pull request merge commits etc). GPG public key of other git hosting services can be imported so that their signed commits/tags can be trusted&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WcS3i8Pp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xc08zujp0d0peudtdr58.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WcS3i8Pp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xc08zujp0d0peudtdr58.png" alt="Image description" width="880" height="644"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Able to cache cloned repository to improve CI/CD speed
&lt;/h2&gt;

&lt;p&gt;Repository can now be cloned into a sub directory under job workspace, and can be cached to improve speed. For instance a fresh clone of Linux repository takes 5 minutes, while updating a cached one takes 5 seconds&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EHnKHD0k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ryiwuepnc6k7e05gtbtl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EHnKHD0k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ryiwuepnc6k7e05gtbtl.png" alt="Image description" width="880" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Other improvements
&lt;/h2&gt;

&lt;p&gt;For a complete list of changes, please check the &lt;a href="https://code.onedev.io/projects/160/builds/2475/fixed-issues?query=%22State%22+is+%22Released%22+order+by+%22Type%22+asc+and+%22Priority%22+desc"&gt;release notes&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>git</category>
    </item>
    <item>
      <title>Effortless OneDev CI/CD Farm with Agents</title>
      <dc:creator>Robin Shen</dc:creator>
      <pubDate>Thu, 10 Feb 2022 08:17:35 +0000</pubDate>
      <link>https://dev.to/robinshine/effortless-onedev-cicd-farm-with-agents-2gd4</link>
      <guid>https://dev.to/robinshine/effortless-onedev-cicd-farm-with-agents-2gd4</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;From day 1, &lt;a href="https://github.com/theonedev/onedev"&gt;OneDev&lt;/a&gt; is able to run CI/CD jobs as pods on Kubernetes clusters. In case you do not have a Kubernetes cluster, it is now possible to install OneDev agents on other machines to get a CI/CD farm. Agent is designed to be maintenance free: the server will push updates to agents automatically when it is upgraded.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Set&amp;nbsp;Up
&lt;/h2&gt;

&lt;p&gt;Let's go through an example to see how to set up the CI/CD farm&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Start OneDev server with below command on Linux or Mac (this command is for demonstration purpose, follow installation guide for production use):&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v $(pwd)/onedev:/opt/onedev -p 6610:6610 -p 6611:6611 1dev/server
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open browser to visit &lt;a href="http://localhost:6610"&gt;http://localhost:6610&lt;/a&gt; to complete the setup&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Visit agent management page, click the add agent button, and show the docker command to run agent:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PlQKb-UF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/igkp70c8u5ql3kgs3b5u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PlQKb-UF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/igkp70c8u5ql3kgs3b5u.png" alt="Image description" width="880" height="581"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy the command to your terminal (Linux/Mac), change serverUrl environment to http://:6610 (do not use localhost here as otherwise OneDev agent running inside container can not access OneDev server), and run the command to start agent. If connected, the agent will be online in agent management page like below:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k8P6MvzG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1v9uq5qwh88zw89ctr12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k8P6MvzG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1v9uq5qwh88zw89ctr12.png" alt="Image description" width="720" height="472"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now let's create a job executor to use the agent. Switch to job executors page, delete the default auto-discover executor, and create an executor of type Remote Docker Executor, give it a name and leave others as default. Test and save the setting:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P-czp_6f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y323n70a3qiaht5whq6f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P-czp_6f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y323n70a3qiaht5whq6f.png" alt="Image description" width="720" height="553"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a test project, define CI spec with a simple job, and run the job. You will see that the job is running on the agent:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m3s38aAi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nk3ar69y5k0zqldjnblc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m3s38aAi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nk3ar69y5k0zqldjnblc.gif" alt="Image description" width="879" height="557"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When OneDev dispatches jobs to agents, it choses the best one based on agent cpu/memory capacity and job cpu/memory requirement. If no agents satisfies the requirement, the job will be in waiting state.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>git</category>
    </item>
    <item>
      <title>Connect Customer to Developer with OneDev Service Desk</title>
      <dc:creator>Robin Shen</dc:creator>
      <pubDate>Fri, 06 Aug 2021 05:14:24 +0000</pubDate>
      <link>https://dev.to/robinshine/connect-customer-to-developer-with-onedev-service-desk-526g</link>
      <guid>https://dev.to/robinshine/connect-customer-to-developer-with-onedev-service-desk-526g</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://onedev.io"&gt;OneDev&lt;/a&gt; is a performant open source git server, with customizable issue workflow and built-in CI/CD.&lt;/p&gt;

&lt;p&gt;Version 4.9 comes with the service desk feature, which allows your customers to submit tickets via email without the need to have a OneDev account. These tickets can then be created in desired projects, and assigned to appropriate staffs in your team. All further discussions over the tickets can take place completely in email, with  discussion contents posted to ticket as comments. Customer will also get notifications when there are any ticket activities, for instance, when a relevant release is created or deployed.&lt;/p&gt;

&lt;h1&gt;
  
  
  Feature Highlights
&lt;/h1&gt;

&lt;p&gt;Before diving into service desk setup, let’s check what the service desk can do for you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Customer can either send ticket to a uniformed email address, for instance &lt;em&gt;&lt;a href="mailto:support@yourcomany.com"&gt;support@yourcomany.com&lt;/a&gt;&lt;/em&gt;, or send to project specific one like &lt;em&gt;&lt;a href="mailto:support+yourproject@yourcompany.com"&gt;support+yourproject@yourcompany.com&lt;/a&gt;&lt;/em&gt;. Tickets sent to uniformed email address will be created in default project of the customer, and you can move them to other projects later if desired.&lt;/li&gt;
&lt;li&gt;Customer can only create tickets in authorized projects. Authorization is done by checking customer email address.&lt;/li&gt;
&lt;li&gt;All recipients of the email will be added to watch list of created ticket, so that they can be notified of ticket activities and join the discussions.&lt;/li&gt;
&lt;li&gt;Html format and attachments of the email are carefully handled to achieve same experience at Email side and web UI side.&lt;/li&gt;
&lt;li&gt;For different customers, you may configure different ticket properties such as priorities and assignees.&lt;/li&gt;
&lt;li&gt;All emails relating to a particular ticket will be organized into a single thread to track discussion context, as well as reduce notification noises. Users can also unsubscribe from the ticket.&lt;/li&gt;
&lt;li&gt;Content of the ticket notification email can be customized.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  How to Set Up
&lt;/h1&gt;

&lt;p&gt;First of all, you need to enable mail setting and its check incoming email option:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6cub0d5G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5xs7ky9iwv7bldvrcbui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6cub0d5G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5xs7ky9iwv7bldvrcbui.png" alt="Alt Text" width="800" height="674"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also &lt;a href="https://en.wikipedia.org/wiki/Email_address#Subaddressing"&gt;subaddressing&lt;/a&gt; needs to be enabled at your mail server for system email address. Test mail sending and receiving at bottom of the page to make sure it works. &lt;/p&gt;

&lt;p&gt;Now OneDev is capable to send ticket notifications via email. Users receiving notification email can also add comments to the ticket by replying the email. Content of the notification email can be customized via notification templates below the mail setting.&lt;/p&gt;

&lt;p&gt;However to be able to create tickets via email, you should also enable the service desk setting like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DktdGira--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/erjckekrteh0erj89zdr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DktdGira--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/erjckekrteh0erj89zdr.png" alt="Alt Text" width="800" height="674"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All done! Now authorized users can send email to system email address to create tickets!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>git</category>
    </item>
  </channel>
</rss>
