Skip to content

Commit ed26daf

Browse files
committed
First draft of commit section (commit created from WYAG)
1 parent ef5e90c commit ed26daf

File tree

1 file changed

+149
-0
lines changed

1 file changed

+149
-0
lines changed

write-yourself-a-git.org

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3033,6 +3033,155 @@ tree object, generate and store the corresponding commit object, and
30333033
update the HEAD branch to the new commit (remember: a branch is just a
30343034
ref to a commit).
30353035

3036+
#+begin_src python :tangle libwyag.py
3037+
argsp = argsubparsers.add_parser("commit", help="Record changes to the repository.")
3038+
3039+
argsp.add_argument("-m",
3040+
metavar="message",
3041+
dest="message",
3042+
help="Message to associate with this commit.")
3043+
#+end_src
3044+
3045+
Before we get to the interesting details, we will need to read git's
3046+
config to get the name of the default user, which we'll use as the
3047+
author of commits. We'll use the same =configparser= library we've
3048+
used to read repo's config.
3049+
3050+
#+begin_src python :tangle libwyag.py
3051+
def gitconfig_read():
3052+
xdg_config_home = os.environ["XDG_CONFIG_HOME"] if "XDG_CONFIG_HOME" in os.environ else "~/.config"
3053+
configfiles = [
3054+
os.path.expanduser(os.path.join(xdg_config_home, "git/config")),
3055+
os.path.expanduser("~/.gitconfig")
3056+
]
3057+
3058+
config = configparser.ConfigParser()
3059+
config.read(configfiles)
3060+
return config
3061+
#+end_src
3062+
3063+
And just a simple function to grab, and format, the user identity:
3064+
3065+
#+begin_src python :tangle libwyag.py
3066+
def gitconfig_user_get(config):
3067+
if "user" in config:
3068+
if "name" in config["user"] and "email" in config["user"]:
3069+
return "{} <{}>".format(config["user"]["name"], config["user"]["email"])
3070+
return None
3071+
#+end_src
3072+
3073+
Now for the interesting part. We first need to build a tree from the
3074+
index. This isn't hard, but notice that while the index is flat (it
3075+
stores full paths for the whole worktree), a tree is a recursive
3076+
structure. What we do is start from the deepest directory, build a
3077+
tree with its contents, write that tree to the repository, add a
3078+
reference to that tree to its parent, and resume at upper levels until
3079+
we hit home. The /last/ tree we'll have built is the only one we'll
3080+
need in the commit, since it's the root and references all others ---
3081+
so this function will simply return its hash.
3082+
3083+
#+begin_src python :tangle libwyag.py
3084+
def tree_from_index(repo, index):
3085+
contents = dict()
3086+
contents[""] = list()
3087+
3088+
# Enumerate entries, and turn them into a dictionary where keys
3089+
# are directories, and values are lists of directory contents.
3090+
for entry in index.entries:
3091+
dirname = os.path.dirname(entry.name)
3092+
3093+
# We create all dictonary entries up to root (""). We need
3094+
# them *all*, because even if a directory holds no files it
3095+
# will contain at least a tree.
3096+
key = dirname
3097+
while key != "":
3098+
if not key in contents:
3099+
contents[key] = list()
3100+
key = os.path.dirname(key)
3101+
3102+
# For now, simply store the entry in the list.
3103+
contents[dirname].append(entry)
3104+
3105+
# Get keys (= directories) and sort them by length, descending.
3106+
# easiest way to traverse the list of paths bottom-up. This means
3107+
# that we'll always encounter a given path before its parent,
3108+
# which is all we need.
3109+
sorted_paths = sorted(contents.keys(), key=len, reverse=True)
3110+
sha = None
3111+
3112+
# We create, and write, trees
3113+
for path in sorted_paths:
3114+
tree = GitTree()
3115+
3116+
for entry in contents[path]:
3117+
if isinstance(entry, GitIndexEntry):
3118+
# We transcode the mode: the entry stores it as integers,
3119+
# we need an octal ASCII representation for the tree.
3120+
leaf_mode = "{:02o}{:04o}".format(entry.mode_type, entry.mode_perms).encode("ascii")
3121+
leaf = GitTreeLeaf(mode = leaf_mode, path=os.path.basename(entry.name), sha=entry.sha)
3122+
else:
3123+
leaf = GitTreeLeaf(mode = b"040000", path=entry[0], sha=entry[1])
3124+
3125+
tree.items.append(leaf)
3126+
3127+
# Write the new tree object.
3128+
sha = object_write(tree, repo)
3129+
3130+
# Add the new tree to its parent.
3131+
parent = os.path.dirname(path)
3132+
base = os.path.basename(path)
3133+
contents[parent].append((base, sha))
3134+
3135+
return sha
3136+
#+end_src
3137+
3138+
The function to create a commit object is simple enough, it just takes a few arguments.
3139+
3140+
#+begin_src python :tangle libwyag.py
3141+
def commit_create(repo, tree, parent, author, timestamp, tz, message):
3142+
commit = GitCommit()
3143+
commit.kvlm[b"tree"] = tree.encode("ascii")
3144+
if parent:
3145+
commit.kvlm[b"parent"] = parent.encode("utf8")
3146+
3147+
author = author + timestamp.strftime(" %s ") + tz
3148+
3149+
commit.kvlm[b"author"] = author.encode("utf8")
3150+
commit.kvlm[b"committer"] = author.encode("utf8")
3151+
commit.kvlm[b''] = message.encode("utf8")
3152+
3153+
return object_write(commit, repo)
3154+
#+end_src
3155+
3156+
And finally, the actual =cmd_commit=, the bridge to the =wyag commit= command:
3157+
3158+
#+begin_src python :tangle libwyag.py
3159+
def cmd_commit(args):
3160+
print(args)
3161+
repo = repo_find()
3162+
index = index_read(repo)
3163+
# Create trees, grab back SHA for the root tree.
3164+
tree = tree_from_index(repo, index)
3165+
3166+
# Create the commit object itself
3167+
commit = commit_create(repo,
3168+
tree,
3169+
object_find(repo, "HEAD"),
3170+
gitconfig_user_get(gitconfig_read()),
3171+
datetime.now(),
3172+
"+0200", # @FIXME
3173+
args.message)
3174+
3175+
# Update HEAD
3176+
active_branch = branch_get_active(repo)
3177+
if active_branch: # If we're on a branch, we update refs/heads/BRANCH
3178+
with open(repo_file(repo, os.path.join("refs/heads", active_branch)), "w") as fd:
3179+
fd.write(commit + "\n")
3180+
else: # Otherwise, we update HEAD itself.
3181+
with open(repo_file(repo, "HEAD"), "w") as fd:
3182+
fd.write("\n")
3183+
#+end_src
3184+
30363185
* Final words
30373186

30383187
** Comments, feedback and issues

0 commit comments

Comments
 (0)