diff --git a/site/_freeze/posts/how-to-commit/index/execute-results/html.json b/site/_freeze/posts/how-to-commit/index/execute-results/html.json new file mode 100644 index 0000000..80c2bde --- /dev/null +++ b/site/_freeze/posts/how-to-commit/index/execute-results/html.json @@ -0,0 +1,11 @@ +{ + "hash": "ccf061c41b5f1bf5bea49a93e2685063", + "result": { + "markdown": "---\ntitle: \"How to write a good commit message\"\nsubtitle: \"Keep easy trackchanges history for your project\"\nauthor: \"alex\"\ndate: \"2025-08-22\"\ncategories: [git, version-control, revision-history]\n# image: \"thumb.png\"\ntoc: TRUE\ntoc-title: \"Table of contents\"\ntoc-depth: 5\n---\n\nThis blog post covers the topic of Git commits, how it is working, and how to use them in an efficient way.\n\n## What is a commit?\n\nA commit is a snapshot of a Git repository at a specific time that will show all the changes made to the content of the repository.\n\nA commit will capture the followings:\n\n- The author's name or ID of the commit\n\n- The list of modified files and the comparison with the files (if any) of the source branch\n\n- The date/time of the commit\n\n- A message that will clearly describe (and possibly detail) the reason of the files' update.\n\nEach commit is associated to a specific identifier code.\n\n## Benefits of commits\n\nDespite the fact that making commit is mandatory when working on a Git repository to update files, there are a lot of benefits in using Git commits.\n\n### Keep clear history of changes\n\nCommonly, each program has a header that contains a *\"Revision history\"* part. Git commits can easily replace this part because it contains the main information of a classic revision history message (author, date and reason for change), associated to the full files comparisons, such as a before/after view.\n\nAll the changes of a single commit are stored in the same place. It means if you work on a single task that request to modify several programs, you can summarize the changes within one message. You do not need to open each separated files one by one to look at what was done, when, and by who.\n\n::: callout-tip\n#### Consolidate all commits of a branch\n\nEven with several commits, you can have a clear view of all the changes when merging to the source branch.\n:::\n\n### Backup save\n\nAs Git is a version control software, and each commit is a snapshot done at a current state, it is easy to go back to a previous version of a branch using commit IDs.\n\n::: callout-caution\n#### Need feedbacks - hash\n\nI think talking about `git checkout` and `git reset` here is not useful. Maybe another blog post could be interesting. What do you think?\n:::\n\n## When to commit?\n\n### Commit one file at a time or several?\n\nThe choice depends on the context and what you want to show. Generally, each commit should be dedicated to a single purpose.\n\nIf the task or feature to add is isolated to a single file, one commit message done at the end of the update can be easily readable and integrated to the history trackchange.\n\nIn the case of a debugging task over several files, one commit message per file could also be useful, or when there is a need to track the evolution of a specific dataset.\n\nWhen a change on a program affects the behavior of other programs that also needs to be updated, one commit for all the modifications can be done. Also when you decide to modify both a program and the associated documentation.\n\n::: callout-caution\n#### Need feedbacks.\n\nNeed feedbacks.\n:::\n\n## How to write a good commit message\n\nAs a history tracking, a commit message should be concise and clearly explain **what** was done and **why**.\n\n### Title\n\nThe title should be very short (around 50 characters) and explain **what was done** on which file(s) (if relevant).\n\nStarting the title with a verb is very useful to explain what was done, for instance:\n\n::: note \n`Add TRTEMFL in adae.R`\n:::\n\n\n\n```{bash}\nAdd TRTEMFL in adae.R\n```\n\n\nA naming convention can be defined at sponsor level to identify keyword, such as `Add`, `Fix`, `Remove`, `Update`, etc. The keyword can also be separated form the rest of the title using symbols such as `:` or `!` (for major or breaking changes). For instance:\n\n\n```{bash}\nAdd: TRTEMFL in adae.R\n```\n\n```{bash}\nUpdate! prim endpoint logic in efficacy (adre.R)\n```\n\n\nUsing tags that to identify the location of changes can also be an option, such as `SDTM:`, `ADAM:` or `TLF:` for instance.\n\n\n```{bash}\nTLF: create overview of AEs\n```\n\n", + "supporting": [ + "index_files" + ], + "filters": [], + "includes": {} + } +} \ No newline at end of file diff --git a/site/_freeze/site_libs/clipboard/clipboard.min.js b/site/_freeze/site_libs/clipboard/clipboard.min.js new file mode 100644 index 0000000..1103f81 --- /dev/null +++ b/site/_freeze/site_libs/clipboard/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=10?setTimeout((function(){e(r,n,s)}),1):(t.update(),n(s))}}},"./src/filter.js":function(t){t.exports=function(t){return t.handlers.filterStart=t.handlers.filterStart||[],t.handlers.filterComplete=t.handlers.filterComplete||[],function(e){if(t.trigger("filterStart"),t.i=1,t.reset.filter(),void 0===e)t.filtered=!1;else{t.filtered=!0;for(var r=t.items,n=0,s=r.length;nv.page,a=new g(t[s],void 0,n),v.items.push(a),r.push(a)}return v.update(),r}m(t.slice(0),e)}},this.show=function(t,e){return this.i=t,this.page=e,v.update(),v},this.remove=function(t,e,r){for(var n=0,s=0,i=v.items.length;s-1&&r.splice(n,1),v},this.trigger=function(t){for(var e=v.handlers[t].length;e--;)v.handlers[t][e](v);return v},this.reset={filter:function(){for(var t=v.items,e=t.length;e--;)t[e].filtered=!1;return v},search:function(){for(var t=v.items,e=t.length;e--;)t[e].found=!1;return v}},this.update=function(){var t=v.items,e=t.length;v.visibleItems=[],v.matchingItems=[],v.templater.clear();for(var r=0;r=v.i&&v.visibleItems.lengthe},innerWindow:function(t,e,r){return t>=e-r&&t<=e+r},dotted:function(t,e,r,n,s,i,a){return this.dottedLeft(t,e,r,n,s,i)||this.dottedRight(t,e,r,n,s,i,a)},dottedLeft:function(t,e,r,n,s,i){return e==r+1&&!this.innerWindow(e,s,i)&&!this.right(e,n)},dottedRight:function(t,e,r,n,s,i,a){return!t.items[a-1].values().dotted&&(e==n&&!this.innerWindow(e,s,i)&&!this.right(e,n))}};return function(e){var n=new i(t.listContainer.id,{listClass:e.paginationClass||"pagination",item:e.item||"
  • ",valueNames:["page","dotted"],searchClass:"pagination-search-that-is-not-supposed-to-exist",sortClass:"pagination-sort-that-is-not-supposed-to-exist"});s.bind(n.listContainer,"click",(function(e){var r=e.target||e.srcElement,n=t.utils.getAttribute(r,"data-page"),s=t.utils.getAttribute(r,"data-i");s&&t.show((s-1)*n+1,n)})),t.on("updated",(function(){r(n,e)})),r(n,e)}}},"./src/parse.js":function(t,e,r){t.exports=function(t){var e=r("./src/item.js")(t),n=function(r,n){for(var s=0,i=r.length;s0?setTimeout((function(){e(r,s)}),1):(t.update(),t.trigger("parseComplete"))};return t.handlers.parseComplete=t.handlers.parseComplete||[],function(){var e=function(t){for(var e=t.childNodes,r=[],n=0,s=e.length;n]/g.exec(t)){var e=document.createElement("tbody");return e.innerHTML=t,e.firstElementChild}if(-1!==t.indexOf("<")){var r=document.createElement("div");return r.innerHTML=t,r.firstElementChild}}},a=function(e,r,n){var s=void 0,i=function(e){for(var r=0,n=t.valueNames.length;r=1;)t.list.removeChild(t.list.firstChild)},function(){var r;if("function"!=typeof t.item){if(!(r="string"==typeof t.item?-1===t.item.indexOf("<")?document.getElementById(t.item):i(t.item):s()))throw new Error("The list needs to have at least one item on init otherwise you'll have to add a template.");r=n(r,t.valueNames),e=function(){return r.cloneNode(!0)}}else e=function(e){var r=t.item(e);return i(r)}}()};t.exports=function(t){return new e(t)}},"./src/utils/classes.js":function(t,e,r){var n=r("./src/utils/index-of.js"),s=/\s+/;Object.prototype.toString;function i(t){if(!t||!t.nodeType)throw new Error("A DOM element reference is required");this.el=t,this.list=t.classList}t.exports=function(t){return new i(t)},i.prototype.add=function(t){if(this.list)return this.list.add(t),this;var e=this.array();return~n(e,t)||e.push(t),this.el.className=e.join(" "),this},i.prototype.remove=function(t){if(this.list)return this.list.remove(t),this;var e=this.array(),r=n(e,t);return~r&&e.splice(r,1),this.el.className=e.join(" "),this},i.prototype.toggle=function(t,e){return this.list?(void 0!==e?e!==this.list.toggle(t,e)&&this.list.toggle(t):this.list.toggle(t),this):(void 0!==e?e?this.add(t):this.remove(t):this.has(t)?this.remove(t):this.add(t),this)},i.prototype.array=function(){var t=(this.el.getAttribute("class")||"").replace(/^\s+|\s+$/g,"").split(s);return""===t[0]&&t.shift(),t},i.prototype.has=i.prototype.contains=function(t){return this.list?this.list.contains(t):!!~n(this.array(),t)}},"./src/utils/events.js":function(t,e,r){var n=window.addEventListener?"addEventListener":"attachEvent",s=window.removeEventListener?"removeEventListener":"detachEvent",i="addEventListener"!==n?"on":"",a=r("./src/utils/to-array.js");e.bind=function(t,e,r,s){for(var o=0,l=(t=a(t)).length;o32)return!1;var a=n,o=function(){var t,r={};for(t=0;t=p;b--){var j=o[t.charAt(b-1)];if(C[b]=0===m?(C[b+1]<<1|1)&j:(C[b+1]<<1|1)&j|(v[b+1]|v[b])<<1|1|v[b+1],C[b]&d){var x=l(m,b-1);if(x<=u){if(u=x,!((c=b-1)>a))break;p=Math.max(1,2*a-c)}}}if(l(m+1,a)>u)break;v=C}return!(c<0)}},"./src/utils/get-attribute.js":function(t){t.exports=function(t,e){var r=t.getAttribute&&t.getAttribute(e)||null;if(!r)for(var n=t.attributes,s=n.length,i=0;i=48&&t<=57}function i(t,e){for(var i=(t+="").length,a=(e+="").length,o=0,l=0;o=i&&l=a?-1:l>=a&&o=i?1:i-a}i.caseInsensitive=i.i=function(t,e){return i((""+t).toLowerCase(),(""+e).toLowerCase())},Object.defineProperties(i,{alphabet:{get:function(){return e},set:function(t){r=[];var s=0;if(e=t)for(;s { + if (categoriesLoaded) { + activateCategory(category); + setCategoryHash(category); + } +}; + +window["quarto-listing-loaded"] = () => { + // Process any existing hash + const hash = getHash(); + + if (hash) { + // If there is a category, switch to that + if (hash.category) { + activateCategory(hash.category); + } + // Paginate a specific listing + const listingIds = Object.keys(window["quarto-listings"]); + for (const listingId of listingIds) { + const page = hash[getListingPageKey(listingId)]; + if (page) { + showPage(listingId, page); + } + } + } + + const listingIds = Object.keys(window["quarto-listings"]); + for (const listingId of listingIds) { + // The actual list + const list = window["quarto-listings"][listingId]; + + // Update the handlers for pagination events + refreshPaginationHandlers(listingId); + + // Render any visible items that need it + renderVisibleProgressiveImages(list); + + // Whenever the list is updated, we also need to + // attach handlers to the new pagination elements + // and refresh any newly visible items. + list.on("updated", function () { + renderVisibleProgressiveImages(list); + setTimeout(() => refreshPaginationHandlers(listingId)); + + // Show or hide the no matching message + toggleNoMatchingMessage(list); + }); + } +}; + +window.document.addEventListener("DOMContentLoaded", function (_event) { + // Attach click handlers to categories + const categoryEls = window.document.querySelectorAll( + ".quarto-listing-category .category" + ); + + for (const categoryEl of categoryEls) { + const category = categoryEl.getAttribute("data-category"); + categoryEl.onclick = () => { + activateCategory(category); + setCategoryHash(category); + }; + } + + // Attach a click handler to the category title + // (there should be only one, but since it is a class name, handle N) + const categoryTitleEls = window.document.querySelectorAll( + ".quarto-listing-category-title" + ); + for (const categoryTitleEl of categoryTitleEls) { + categoryTitleEl.onclick = () => { + activateCategory(""); + setCategoryHash(""); + }; + } + + categoriesLoaded = true; +}); + +function toggleNoMatchingMessage(list) { + const selector = `#${list.listContainer.id} .listing-no-matching`; + const noMatchingEl = window.document.querySelector(selector); + if (noMatchingEl) { + if (list.visibleItems.length === 0) { + noMatchingEl.classList.remove("d-none"); + } else { + if (!noMatchingEl.classList.contains("d-none")) { + noMatchingEl.classList.add("d-none"); + } + } + } +} + +function setCategoryHash(category) { + setHash({ category }); +} + +function setPageHash(listingId, page) { + const currentHash = getHash() || {}; + currentHash[getListingPageKey(listingId)] = page; + setHash(currentHash); +} + +function getListingPageKey(listingId) { + return `${listingId}-page`; +} + +function refreshPaginationHandlers(listingId) { + const listingEl = window.document.getElementById(listingId); + const paginationEls = listingEl.querySelectorAll( + ".pagination li.page-item:not(.disabled) .page.page-link" + ); + for (const paginationEl of paginationEls) { + paginationEl.onclick = (sender) => { + setPageHash(listingId, sender.target.getAttribute("data-i")); + showPage(listingId, sender.target.getAttribute("data-i")); + return false; + }; + } +} + +function renderVisibleProgressiveImages(list) { + // Run through the visible items and render any progressive images + for (const item of list.visibleItems) { + const itemEl = item.elm; + if (itemEl) { + const progressiveImgs = itemEl.querySelectorAll( + `img[${kProgressiveAttr}]` + ); + for (const progressiveImg of progressiveImgs) { + const srcValue = progressiveImg.getAttribute(kProgressiveAttr); + if (srcValue) { + progressiveImg.setAttribute("src", srcValue); + } + progressiveImg.removeAttribute(kProgressiveAttr); + } + } + } +} + +function getHash() { + // Hashes are of the form + // #name:value|name1:value1|name2:value2 + const currentUrl = new URL(window.location); + const hashRaw = currentUrl.hash ? currentUrl.hash.slice(1) : undefined; + return parseHash(hashRaw); +} + +const kAnd = "&"; +const kEquals = "="; + +function parseHash(hash) { + if (!hash) { + return undefined; + } + const hasValuesStrs = hash.split(kAnd); + const hashValues = hasValuesStrs + .map((hashValueStr) => { + const vals = hashValueStr.split(kEquals); + if (vals.length === 2) { + return { name: vals[0], value: vals[1] }; + } else { + return undefined; + } + }) + .filter((value) => { + return value !== undefined; + }); + + const hashObj = {}; + hashValues.forEach((hashValue) => { + hashObj[hashValue.name] = decodeURIComponent(hashValue.value); + }); + return hashObj; +} + +function makeHash(obj) { + return Object.keys(obj) + .map((key) => { + return `${key}${kEquals}${obj[key]}`; + }) + .join(kAnd); +} + +function setHash(obj) { + const hash = makeHash(obj); + window.history.pushState(null, null, `#${hash}`); +} + +function showPage(listingId, page) { + const list = window["quarto-listings"][listingId]; + if (list) { + list.show((page - 1) * list.page + 1, list.page); + } +} + +function activateCategory(category) { + // Deactivate existing categories + const activeEls = window.document.querySelectorAll( + ".quarto-listing-category .category.active" + ); + for (const activeEl of activeEls) { + activeEl.classList.remove("active"); + } + + // Activate this category + const categoryEl = window.document.querySelector( + `.quarto-listing-category .category[data-category='${category}'` + ); + if (categoryEl) { + categoryEl.classList.add("active"); + } + + // Filter the listings to this category + filterListingCategory(category); +} + +function filterListingCategory(category) { + const listingIds = Object.keys(window["quarto-listings"]); + for (const listingId of listingIds) { + const list = window["quarto-listings"][listingId]; + if (list) { + if (category === "") { + // resets the filter + list.filter(); + } else { + // filter to this category + list.filter(function (item) { + const itemValues = item.values(); + if (itemValues.categories !== null) { + const categories = itemValues.categories.split(","); + return categories.includes(category); + } else { + return false; + } + }); + } + } + } +} diff --git a/site/posts/how-to-commit/index.qmd b/site/posts/how-to-commit/index.qmd new file mode 100644 index 0000000..42c4dbc --- /dev/null +++ b/site/posts/how-to-commit/index.qmd @@ -0,0 +1,164 @@ +--- +title: "How to write a good commit message" +subtitle: "Keep easy track changes history for your project" +author: "alex" +date: "2025-08-22" +categories: [git, version-control, revision-history] +# image: "thumb.png" +toc: TRUE +toc-title: "Table of contents" +toc-depth: 5 +--- + +This blog post covers the topic of Git commits, how it is working, and how to use them in an efficient way. + +## What is a commit? + +A commit is a snapshot of a Git repository at a specific time that will show all the changes made to the content of the repository. + +A commit will capture the followings: + +- The author's name or ID of the commit + +- The list of modified files and the comparison with the files (if any) of the source branch + +- The date/time of the commit + +- A message that will clearly describe (and possibly detail) the reason of the files' update. + +Each commit is associated to a specific identifier code. + +## Benefits of commits + +Despite the fact that making commit is mandatory when working on a Git repository to update files, there are a lot of benefits in using Git commits. + +### Keep clear history of changes + +In traditional approaches, each program has a header that contains a *"Revision history"* part. Git commits can easily replace this part because it contains the main information of a classic revision history message (author, date and reason for change), associated to the full files comparisons, such as a before/after view. + +All the changes of a single commit are stored in the same place. It means if you work on a single task that request to modify several programs, you can summarize the changes within one message. You do not need to open each separated files one by one to look at what was done, when, and by who. + +::: callout-tip +#### Consolidate all commits of a branch + +Even with several commits, you can have a clear view of all the changes when merging to the source branch. +::: + +### Backup save + +As Git is a version control software, and each commit is a snapshot done at a current state, it is easy to go back to a previous version of a branch using commit IDs. + +## When to commit? + +### Commit one file at a time or several? + +The choice depends on the context and what you want to show. Generally, each commit should be dedicated to a single purpose. + +If the task or feature to add is isolated to a single file, one commit message done at the end of the update can be easily readable and integrated to the history trackchange. + +In the case of a debugging task over several files, one commit message per file could also be useful, or when there is a need to track the evolution of a specific dataset. + +When a change on a program affects the behavior of other programs that also needs to be updated, one commit for all the modifications can be done. Also when you decide to modify both a program and the associated documentation. + +## How to write a good commit message + +As a history tracking, a commit message should be concise and clearly explain **what** was done and **why**. + +It is important to have good commit messages because, as mentioned above, it can replace a classic track change history. The goal is to have messages that will help understanding the changes and will help during the quality control, review or audit steps. + +::: callout-tip +#### Consolidate all commits of a branch + +Even with several commits, you can have a clear view of all the changes when merging to the source branch. +::: + +### Title + +The title should be very short (around 50 characters) and explain **what was done** on which file(s) (if relevant). + +Starting the title with a verb is very useful to explain what was done, for instance: + +::: note +`Add TRTEMFL in adae.R` +::: + +A naming convention can be defined at sponsor level to identify keyword, such as `Add`, `Fix`, `Remove`, `Update`, etc. The keyword can also be separated form the rest of the title using symbols such as `:` or `!` (for major or breaking changes). For instance: + +::: note +`Add: TRTEMFL in adae.R` +::: + +or + +::: note +`Update! prim endpoint logic in efficacy (adre.R)` +::: + +Using tags that to identify the location of changes can also be an option, such as `SDTM:`, `ADAM:` or `TLF:` for instance. + +::: note +`TLF: create overview of AEs` +::: + +::: callout-tip +#### Keywords and source code management platforms + +So websites such as GitHub are able to recognize some keywords such as `closes` in the title, that will automatically perform an action on the issue or branch (for instance, `closes` will close the issue). +::: + +### Detail + +The detail explains **why** the commit needed to be done, the methodological or technical context, or any relevant information that needs to understand the commit. + +It should contains, when relevant, the source (such as a new version of the statistical analysis plan, a decision taken in a meeting minutes, an email, etc), the impact (a modification on a ADaM dataset will have an impact on the TL&F describing the updated variables) or the associated logic. + +It should be informative but also concise. + +Using source code management platforms (GitHub, GitLab, BitBucket, etc) allows a direct link to issues in the title or detail of a commit (for instance `Linked to #35`) + +Here is an example of a commit message including a title and the body (detail): + +::: note +**`Add! ANLzzFL in ADxx, ADyy, and ADzz for efficacy`** + +`Implements ANLzzFL variable in ADxx, ADyy, and ADzz to identify records for inclusion in the new efficacy analysis introduced in SAP release 3.0 (part 4.5.1, [title]).` + +`This flag marks the specific records relevant to the new analysis.` + +`Impact: downstream efficacy tables and participant selection for regulatory outputs.` +::: + +::: callout-tip +#### Conventional commits + +[conventionalcommits.org](https://www.conventionalcommits.org/en/v1.0.0/) gives tips and guidance for writing commit messages. +::: + +::: callout-tip +#### Using AI to improve commits +Tools such as GitHub Copilot can help to write a good commit message by reviewing a branch and what updates were done. +::: + +## Examples of bad commit messages + +### Single keyword + +A message such as `fix` with no addition or detail is too vague. It is not possible to understand directly which file has been fixed and why. + +### Too wordy title + +The following title is way too long and difficult to read easily: + +` +Update participant demographics summary dataset (ADSL) to include additional needed variables for geographical regions groups requested during the study team meeting on September 1st 2025 following the last version (1.2) of the SAP. +` + +A better way would be to have a title like this one: + +`Add ADSL.REGION1(N)` + +And a detailed description such as this one: + +`Following study team meeting (01-SEP-2025).` + +`Done as per SAP (v1.2).`