{"thread":{"id":"18520","subject":"[PATCH 5/5] p4 example of git-vcs API for fetch direction","startedAt":"2009-03-25T03:04:19Z","lastAt":"2009-03-25T03:04:19Z","messageCount":1,"participants":["Daniel Barkalow"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"109299","messageId":"alpine.LNX.1.00.0903242303500.19665@iabervon.org","threadId":"18520","inReplyTo":null,"subject":"[PATCH 5/5] p4 example of git-vcs API for fetch direction","fromName":"Daniel Barkalow","fromEmail":"barkalow@iabervon.org","sentAt":"2009-03-25T03:04:19Z","receivedAt":"2009-03-25T03:04:19Z","isPatch":true,"sender":{"key":"barkalow@iabervon.org","avatar":"https://avatars.githubusercontent.com/u/55364219?v=4"},"body":"---\n Documentation/git-vcs-p4.txt |   33 ++\n Makefile                     |   25 +\n p4-notes                     |   33 ++\n vcs-p4/p4client-api.cc       |  169 ++++++\n vcs-p4/p4client.c            |  137 +++++\n vcs-p4/p4client.h            |   38 ++\n vcs-p4/vcs-p4.c              | 1229 ++++++++++++++++++++++++++++++++++++++++++\n vcs-p4/vcs-p4.h              |  128 +++++\n 8 files changed, 1792 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/git-vcs-p4.txt\n create mode 100644 p4-notes\n create mode 100644 vcs-p4/p4client-api.cc\n create mode 100644 vcs-p4/p4client.c\n create mode 100644 vcs-p4/p4client.h\n create mode 100644 vcs-p4/vcs-p4.c\n create mode 100644 vcs-p4/vcs-p4.h\n\ndiff --git a/Documentation/git-vcs-p4.txt b/Documentation/git-vcs-p4.txt\nnew file mode 100644\nindex 0000000..4039d24\n--- /dev/null\n+++ b/Documentation/git-vcs-p4.txt\n@@ -0,0 +1,33 @@\n+Config\n+------\n+\n+vcs-p4.port::\n+\tThe value to use for P4PORT\n+\n+vcs-p4.client::\n+\tThe value to use for P4CLIENT\n+\n+vcs-p4.codelineformat::\n+\tA regular expression to match valid codelines; a codeline is a\n+\tdirectory that contains exactly those files that belong to a\n+\tversion of a project. Importing history with integrations will\n+\tgenerally discover codelines not explicitly marked to be\n+\timported, found when a file in a known codeline, whose full\n+\tpath is therefore the codeline path plus a relative path, is\n+\tintegrated from a file with a name that ends with that\n+\trelative path. However, files will sometimes be integrated\n+\tfrom non-codelines (that is, from a directory that contains\n+\tunrelated files whose history should not be tracked), and this\n+\toption can be used to ignore some directories.\n+\n+\tNote that, properly, the history of the individual files from\n+\ta non-codeline which got integrated into a codeline should\n+\tcontribute but that this is not presently supported.\n+\n+remotes.*.url::\n+\tThe perforce location of a codeline to track. Other codelines\n+\tmay be discovered by git-vcs-p4, but it will make no attempt\n+\tto get versions in these locations more recent than the last\n+\tversions that contribute at present to the tracked codelines,\n+\tand it will not make them available for matching in \"fetch\"\n+\tpatterns.\ndiff --git a/Makefile b/Makefile\nindex 43a8364..b148b34 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -320,6 +320,7 @@ PROGRAMS += git-unpack-file$X\n PROGRAMS += git-update-server-info$X\n PROGRAMS += git-upload-pack$X\n PROGRAMS += git-var$X\n+PROGRAMS += git-vcs-p4$X\n \n # List built-in command $C whose implementation cmd_$C() is not in\n # builtin-$C.o but is linked in as part of some other command.\n@@ -1106,6 +1107,7 @@ endif\n ifneq ($(findstring $(MAKEFLAGS),s),s)\n ifndef V\n \tQUIET_CC       = @echo '   ' CC $@;\n+\tQUIET_CXX      = @echo '   ' CXX $@;\n \tQUIET_AR       = @echo '   ' AR $@;\n \tQUIET_LINK     = @echo '   ' LINK $@;\n \tQUIET_BUILT_IN = @echo '   ' BUILTIN $@;\n@@ -1288,12 +1290,16 @@ git.o git.spec \\\n \t$(patsubst %.perl,%,$(SCRIPT_PERL)) \\\n \t: GIT-VERSION-FILE\n \n+vcs-p4/%.o: ALL_CFLAGS += -I.\n+\n %.o: %.c GIT-CFLAGS\n \t$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<\n %.s: %.c GIT-CFLAGS\n \t$(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $<\n %.o: %.S\n \t$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<\n+%.o: %.cc GIT-CFLAGS\n+\t$(QUIET_CXX)$(CXX) -o $*.o -c $(ALL_CFLAGS) $<\n \n exec_cmd.o: exec_cmd.c GIT-CFLAGS\n \t$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \\\n@@ -1329,6 +1335,24 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)\n \n+P4API_BASE=/home/barkalow/stuff/p4api-2008.1.158777\n+\n+ifdef P4API_BASE\n+P4_IMPL=p4client-api\n+\n+vcs-p4/p4client-api.o: ALL_CFLAGS += -I$(P4API_BASE)/include\n+P4_LINK=$(CXX)\n+P4LIBS=-L$(P4API_BASE)/lib -lclient -lrpc -lsupp\n+else\n+P4_IMPL=p4client\n+P4_LINK=$(CC)\n+endif\n+\n+git-vcs-p4$X: LIBS += $(P4LIBS)\n+git-vcs-p4$X: vcs-p4/vcs-p4.o vcs-p4/$(P4_IMPL).o $(GITLIBS)\n+\t$(QUIET_LINK)$(P4_LINK) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n+\t\t$(LIBS)\n+\n $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)\n $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h)\n builtin-revert.o wt-status.o: wt-status.h\n@@ -1583,6 +1607,7 @@ distclean: clean\n \n clean:\n \t$(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \\\n+\t\tvcs-p4/*.o \\\n \t\t$(LIB_FILE) $(XDIFF_LIB)\n \t$(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X\n \t$(RM) $(TEST_PROGRAMS)\ndiff --git a/p4-notes b/p4-notes\nnew file mode 100644\nindex 0000000..bd95903\n--- /dev/null\n+++ b/p4-notes\n@@ -0,0 +1,33 @@\n+People using branches in p4 work like svn, except that the branches\n+are not rooted at predictable places. Furthermore, there is not a\n+uniform tree layout within a depot.\n+\n+Therefore, in order to generate a git repository from p4, it is\n+necessary to specify a root within the depot as the working tree root\n+in git. On the other hand, it should be possible to determine from the\n+p4 history what portions of the depot outside of the root should be\n+considered as branches, as it tracks \"integrations\".\n+\n+In theory, anyway, it should even be possible to produce a git\n+repository with submodules when a similar thing has been done with\n+integrations in p4, by determining that there are integrations into a\n+subdirectory of the root.\n+\n+---\n+\n+Overview of operation:\n+\n+ - Allocate codeline\n+ - Import codeline\n+   - use p4_filelog to find the files and their revisions in the codeline\n+   - For each file,\n+\n+---\n+Saving processed state\n+\n+ - Record for each codeline\n+   - What are all the changesets?\n+\n+ - Record for each codeline/changeset\n+   - What's the commit\n+\ndiff --git a/vcs-p4/p4client-api.cc b/vcs-p4/p4client-api.cc\nnew file mode 100644\nindex 0000000..df8b606\n--- /dev/null\n+++ b/vcs-p4/p4client-api.cc\n@@ -0,0 +1,169 @@\n+extern \"C\" {\n+#include \"p4client.h\"\n+}\n+\n+#include <p4/clientapi.h>\n+\n+class VCSClientUser : public ClientUser {\n+  virtual void OutputInfo(char level, const char *data);\n+  virtual void OutputBinary(const char *data, int length);\n+  virtual void OutputText(const char *data, int length);\n+public:\n+  void *data;\n+  void (*info_cb)(void *, int, const char *);\n+  void (*form_cb)(void *, const char *, const char *);\n+\n+  void (*buffer_cb)(void *, const char *buffer, int length);\n+\n+  void clear() {\n+    info_cb = NULL;\n+    form_cb = NULL;\n+    buffer_cb = NULL;\n+  }\n+};\n+\n+static ClientApi client;\n+static VCSClientUser ui;\n+\n+void p4_init(const char *const *env)\n+{\n+  Error e;\n+  StrBuf msg;\n+\n+  while (*env) {\n+    if (!strncmp(*env, \"P4PORT=\", 7))\n+      client.SetPort((*env) + 7);\n+    env++;\n+  }\n+\n+  client.Init(&e);\n+  if (e.Test()) {\n+    e.Fmt(&msg);\n+    fprintf(stderr, msg.Text());\n+    exit(1);\n+  }\n+}\n+\n+void VCSClientUser::OutputBinary(const char *buffer, int length)\n+{\n+  if (buffer_cb) {\n+    buffer_cb(data, buffer, length);\n+  } else\n+    fprintf(stderr, \"Unexpected binary of length %d\\n\", length);\n+}\n+\n+void VCSClientUser::OutputText(const char *buffer, int length)\n+{\n+  if (buffer_cb) {\n+    buffer_cb(data, buffer, length);\n+  } else\n+    fprintf(stderr, \"Unexpected text of length %d\\n\", length);\n+}\n+\n+void VCSClientUser::OutputInfo(char level, const char *line)\n+{\n+  if (info_cb)\n+    info_cb(data, level - '0', line);\n+  else if (form_cb) {\n+    struct strbuf key;\n+    struct strbuf value;\n+\n+    strbuf_init(&key, 0);\n+    strbuf_init(&value, 0);\n+\n+    const char *eol = NULL;\n+    for (; *line; line = eol + 1) {\n+      const char *eok;\n+\n+      eol = strchr(line, '\\n');\n+      if (!eol)\n+\tbreak;\n+      if (eol == line || line[0] == '#')\n+\tcontinue;\n+      eok = strchr(line, ':');\n+      if (!eok)\n+\tcontinue;\n+      strbuf_reset(&key);\n+      strbuf_reset(&value);\n+      strbuf_add(&key, line, eok - line);\n+      if (eok[1] == '\\t') {\n+\tstrbuf_add(&value, eok + 2, eol - (eok + 2));\n+      } else if (eok[1] == '\\n') {\n+\tfor (line = eol + 1; *line && line[0] != '\\n'; line = eol + 1) {\n+\t  eol = strchr(line, '\\n');\n+\t  strbuf_add(&value, line + 1, eol - (line + 1) + 1);\n+\t}\n+      }\n+      form_cb(data, key.buf, value.buf);\n+    }\n+    strbuf_release(&key);\n+    strbuf_release(&value);\n+  } else\n+    fprintf(stderr, \"Unexpected info: %c ... %s\\n\", level, line);\n+}\n+\n+int p4_call(int fds[], const char *arg0, int argc, char *const *argv)\n+{\n+  ui.data = NULL;\n+  ui.clear();\n+  client.SetArgv(argc, argv);\n+  client.Run(arg0, &ui);\n+  p4_fini();\n+  exit(1);\n+  return 0;\n+}\n+\n+int _p4_call_info(const char *arg0, int argc, char *const *argv,\n+\t\t  void *data,\n+\t\t  void (*cb)(void *data, int level, const char *line))\n+{\n+  ui.clear();\n+  ui.data = data;\n+  ui.info_cb = cb;\n+  client.SetArgv(argc, argv);\n+  client.Run(arg0, &ui);\n+  return 0;\n+}\n+\n+int _p4_call_form(const char *arg0, int argc, char *const *argv,\n+\t\t  void *data,\n+\t\t  void (*cb)(void *data, const char *key, const char *value))\n+{\n+  ui.clear();\n+  ui.data = data;\n+  ui.form_cb = cb;\n+  client.SetArgv(argc, argv);\n+  client.Run(arg0, &ui);\n+  return 0;\n+}\n+\n+int _p4_call_buffer(const char *arg0, int argc, char *const *argv,\n+\t\t    void *data,\n+\t\t    void (*cb)(void *data, const char *buffer, int length))\n+{\n+  ui.clear();\n+  ui.data = data;\n+  ui.buffer_cb = cb;\n+  client.SetArgv(argc, argv);\n+  client.Run(arg0, &ui);\n+  return 0;\n+}\n+\n+int p4_complete()\n+{\n+  return 0;\n+}\n+\n+int p4_fini()\n+{\n+  Error e;\n+  StrBuf msg;\n+\n+  client.Final(&e);\n+  if (e.Test()) {\n+    e.Fmt(&msg);\n+    fprintf(stderr, msg.Text());\n+    exit(1);\n+  }\n+  return 0;\n+}\ndiff --git a/vcs-p4/p4client.c b/vcs-p4/p4client.c\nnew file mode 100644\nindex 0000000..96ccdf9\n--- /dev/null\n+++ b/vcs-p4/p4client.c\n@@ -0,0 +1,137 @@\n+#include \"p4client.h\"\n+\n+#include \"cache.h\"\n+#include \"run-command.h\"\n+\n+static const char *const *envp;\n+\n+void p4_init(const char *const *env)\n+{\n+\tenvp = env;\n+}\n+\n+static struct child_process child;\n+\n+int p4_call(int fds[], const char *arg0, int argc, char *const *argv)\n+{\n+\tint i;\n+\tmemset(&child, 0, sizeof(child));\n+\tif (fds) {\n+\t\tchild.in = -1;\n+\t\tchild.out = -1;\n+\t} else {\n+\t\tchild.no_stdin = 1;\n+\t\tchild.no_stdout = 1;\n+\t}\n+\tchild.err = 0;\n+\tchild.argv = xcalloc(argc + 3, sizeof(*argv));\n+\tchild.argv[0] = \"p4\";\n+\tchild.argv[1] = arg0;\n+\tchild.env = envp;\n+\tfor (i = 0; i < argc; i++)\n+\t\tchild.argv[i + 2] = argv[i];\n+\tchild.argv[argc + 2] = NULL;\n+\tstart_command(&child);\n+\tif (fds) {\n+\t\tfds[0] = child.in;\n+\t\tfds[1] = child.out;\n+\t}\n+\treturn 0;\n+}\n+\n+int _p4_call_info(const char *arg0, int argc, char *const *argv, void *data,\n+\t\t  void (*cb)(void *data, int level, const char *line))\n+{\n+\tint fds[2];\n+\tstruct strbuf line;\n+\tFILE *input;\n+\n+\tif (p4_call(fds, arg0, argc, argv))\n+\t\treturn -1;\n+\n+\tstrbuf_init(&line, 0);\n+\tinput = fdopen(fds[1], \"r\");\n+\twhile (!strbuf_getline(&line, input, '\\n')) {\n+\t\tint level = 0;\n+\t\tchar *posn = line.buf;\n+\t\twhile (!prefixcmp(posn, \"... \")) {\n+\t\t\tposn += 4;\n+\t\t\tlevel++;\n+\t\t}\n+\t\tcb(data, level, posn);\n+\t}\n+\tp4_complete();\n+\treturn 0;\n+}\n+\n+int _p4_call_form(const char *arg0, int argc, char *const *argv, void *data,\n+\t\t  void (*cb)(void *data, const char *key, const char *value))\n+{\n+\tint fds[2];\n+\tstruct strbuf line;\n+\tstruct strbuf key;\n+\tstruct strbuf value;\n+\tFILE *input;\n+\n+\tif (p4_call(fds, arg0, argc, argv))\n+\t\treturn -1;\n+\n+\tstrbuf_init(&line, 0);\n+\tstrbuf_init(&key, 0);\n+\tstrbuf_init(&value, 0);\n+\tinput = fdopen(fds[1], \"r\");\n+\tfor (; !strbuf_getline(&line, input, '\\n'); strbuf_reset(&line)) {\n+\t\tconst char *eok;\n+\n+\t\tif (!line.buf[0] || line.buf[0] == '#')\n+\t\t\tcontinue;\n+\t\teok = strchr(line.buf, ':');\n+\t\tif (!eok)\n+\t\t\tcontinue;\n+\t\tstrbuf_reset(&key);\n+\t\tstrbuf_reset(&value);\n+\t\tstrbuf_add(&key, line.buf, eok - line.buf);\n+\t\tif (eok[1] == '\\t')\n+\t\t\tstrbuf_addstr(&value, eok + 2);\n+\t\telse {\n+\t\t\tstrbuf_reset(&line);\n+\t\t\twhile (!strbuf_getline(&line, input, '\\n') && line.len) {\n+\t\t\t\tstrbuf_addstr(&value, line.buf + 1);\n+\t\t\t\tstrbuf_addch(&value, '\\n');\n+\t\t\t\tstrbuf_reset(&line);\n+\t\t\t}\n+\t\t}\n+\t\tcb(data, key.buf, value.buf);\n+\t}\n+\tp4_complete();\n+\treturn 0;\n+}\n+\n+int _p4_call_buffer(const char *arg0, int argc, char *const *argv, void *data,\n+\t\t    void (*cb)(void *data, const char *buffer, int len))\n+{\n+\tint fds[2];\n+\tstruct strbuf block;\n+\tif (p4_call(fds, arg0, argc, argv))\n+\t\treturn -1;\n+\tstrbuf_init(&block, 0);\n+\tstrbuf_read(&block, fds[1], 0);\n+\tcb(data, block.buf, block.len);\n+\tp4_complete();\n+\treturn 0;\n+}\n+\n+int p4_complete(void)\n+{\n+\tif (!child.no_stdin)\n+\t\tclose(child.in);\n+\tif (!child.no_stdout)\n+\t\tclose(child.out);\n+\tfinish_command(&child);\n+\treturn 0;\n+}\n+\n+int p4_fini(void)\n+{\n+\treturn 0;\n+}\ndiff --git a/vcs-p4/p4client.h b/vcs-p4/p4client.h\nnew file mode 100644\nindex 0000000..d0e4ccd\n--- /dev/null\n+++ b/vcs-p4/p4client.h\n@@ -0,0 +1,38 @@\n+#ifndef P4CLIENT_H\n+#define P4CLIENT_H\n+\n+#include <stdlib.h>\n+#include <stdio.h>\n+#include <string.h>\n+#include \"strbuf.h\"\n+\n+void p4_init(const char *const *env);\n+\n+int p4_call(int fds[], const char *arg0, int argc, char *const *argv);\n+\n+#define p4_call_info(arg0, argc, argv, data, cb) \\\n+\t(0 ? ((*(cb))((data), 0, NULL), 1) : \\\n+\t _p4_call_info(arg0, argc, argv, (void *)data, (void (*)(void *, int, const char *)) cb))\n+\n+#define p4_call_form(arg0, argc, argv, data, cb) \\\n+\t(0 ? ((*(cb))((data), NULL, NULL), 1) : \\\n+\t _p4_call_form(arg0, argc, argv, (void *)data, (void (*)(void *, const char *, const char *)) cb))\n+\n+#define p4_call_buffer(arg0, argc, argv, data, cb) \\\n+\t(0 ?  ((*(cb))((data), NULL, 0), 1) : \\\n+\t _p4_call_buffer(arg0, argc, argv, (void *)data, (void (*)(void *, const char *, int)) cb))\n+\n+int p4_complete();\n+\n+int p4_fini();\n+\n+int _p4_call_info(const char *arg0, int argc, char *const *argv, void *data,\n+\t\t  void (*cb)(void *data, int level, const char *line));\n+\n+int _p4_call_form(const char *arg0, int argc, char *const *argv, void *data,\n+\t\t  void (*cb)(void *data, const char *key, const char *value));\n+\n+int _p4_call_buffer(const char *arg0, int argc, char *const *argv, void *data,\n+\t\t    void (*cb)(void *data, const char *buffer, int len));\n+\n+#endif\ndiff --git a/vcs-p4/vcs-p4.c b/vcs-p4/vcs-p4.c\nnew file mode 100644\nindex 0000000..36f7d3f\n--- /dev/null\n+++ b/vcs-p4/vcs-p4.c\n@@ -0,0 +1,1229 @@\n+#include \"cache.h\"\n+#include \"vcs-p4.h\"\n+#include \"strbuf.h\"\n+#include \"remote.h\"\n+#include \"commit.h\"\n+#include \"tree.h\"\n+#include \"tree-walk.h\"\n+#include \"diff.h\"\n+\n+#include \"p4client.h\"\n+\n+#include <string.h>\n+\n+/** Should we try to find codelines that branch off of the relevant\n+ * ones, for future reference? This lets us find new things in\n+ * ls-remote without making the user tell us.\n+ **/\n+static int find_new_codelines;\n+\n+static int ignore_codeline_nr;\n+static int ignore_codeline_alloc;\n+static char **ignore_codelines;\n+\n+static int prints_done = 0;\n+\n+static regex_t *codeline_regex;\n+\n+#define CODELINE_TAG \"Codeline: \"\n+#define CHANGE_TAG \"Changelist: \"\n+\n+#define LIST_P4_OPERATIONS 0\n+\n+/** List functions **/\n+\n+static void add_to_revision_list(struct p4_revision_list **list,\n+\t\t\t\t struct p4_revision *revision)\n+{\n+\twhile (*list)\n+\t\tlist = &(*list)->next;\n+\t*list = xcalloc(1, sizeof(**list));\n+\t(*list)->revision = revision;\n+}\n+\n+static struct p4_revision_list *copy_revision_list(struct p4_revision_list *lst)\n+{\n+\tstruct p4_revision_list *ret, **posn = &ret;\n+\twhile (lst) {\n+\t\t*posn = xcalloc(1, sizeof(**posn));\n+\t\t(*posn)->revision = lst->revision;\n+\t\tposn = &((*posn)->next);\n+\t\tlst = lst->next;\n+\t}\n+\treturn ret;\n+}\n+\n+/** Functions to find or create representations **/\n+\n+static struct p4_depot *get_depot(void)\n+{\n+\tstruct p4_depot *depot = xcalloc(1, sizeof(*depot));\n+\tdepot->next_mark = 1;\n+\treturn depot;\n+}\n+\n+static struct p4_changeset *get_changeset(struct p4_codeline *codeline,\n+\t\t\t\t\t  long number);\n+\n+static char *codeline_to_refname(const char *path) {\n+\tstruct strbuf buf;\n+\tif (prefixcmp(path, \"//\"))\n+\t\treturn NULL;\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addf(&buf, \"refs/p4/%s\", path + 2);\n+\treturn strbuf_detach(&buf, NULL);\n+}\n+\n+static char *refname_to_codeline(const char *refname) {\n+\tstruct strbuf buf;\n+\tif (prefixcmp(refname, \"refs/p4/\"))\n+\t\treturn NULL;\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addf(&buf, \"//%s\", refname + strlen(\"refs/p4/\"));\n+\treturn strbuf_detach(&buf, NULL);\n+}\n+\n+static struct p4_codeline *get_codeline(struct p4_depot *depot, const char *path)\n+{\n+\tstruct p4_codeline **posn, *codeline;\n+\tint i;\n+\tunsigned char sha1[20];\n+\n+\tif (codeline_regex && regexec(codeline_regex, path, 0, NULL, 0))\n+\t\treturn NULL;\n+\n+\tfor (posn = &depot->codelines; *posn; posn = &(*posn)->next)\n+\t\tif (!strcmp(path, (*posn)->path))\n+\t\t\treturn *posn;\n+\tcodeline = xcalloc(1, sizeof(*codeline));\n+\tcodeline->depot = depot;\n+\tcodeline->path = xstrdup(path);\n+\n+\tfor (i = 0; i < ignore_codeline_nr; i++)\n+\t\tif (!strcmp(path, ignore_codelines[i]))\n+\t\t\tcodeline->ignore = 1;\n+\n+\tcodeline->refname = codeline_to_refname(path);\n+\tif (!get_sha1(codeline->refname, sha1)) {\n+\t\tstruct commit *commit = lookup_commit(sha1);\n+\t\tchar *field;\n+\t\tparse_commit(commit);\n+\t\tfield = strstr(commit->buffer, CHANGE_TAG);\n+\t\tif (!field) {\n+\t\t\tfprintf(stderr, \"Couldn't find changeset line in commit\\n\");\n+\t\t} else {\n+\t\t\tstruct p4_changeset *changeset;\n+\t\t\tcodeline->finished_changeset =\n+\t\t\t\tatoi(field + strlen(CHANGE_TAG));\n+\t\t\tchangeset = get_changeset(codeline, codeline->finished_changeset);\n+\t\t\tchangeset->commit = commit;\n+\t\t\tcodeline->history = changeset;\n+\t\t}\n+\t}\n+\t*posn = codeline;\n+\treturn codeline;\n+}\n+\n+static struct p4_codeline *find_codeline(struct p4_depot *depot, const char *path)\n+{\n+\tstruct p4_codeline **posn;\n+\tfor (posn = &depot->codelines; *posn; posn = &(*posn)->next)\n+\t\tif (!prefixcmp(path, (*posn)->path))\n+\t\t\treturn *posn;\n+\treturn NULL;\n+}\n+\n+/** Inserts the changeset at the right place in order for the codeline **/\n+static struct p4_changeset *get_changeset(struct p4_codeline *codeline,\n+\t\t\t\t\t  long number)\n+{\n+\tstruct p4_changeset **posn = &codeline->changesets;\n+\tstruct p4_changeset *changeset, *prev = NULL;\n+\twhile (*posn && (*posn)->number < number) {\n+\t\tprev = *posn;\n+\t\tposn = &(*posn)->next;\n+\t}\n+\tif (*posn && (*posn)->number == number)\n+\t\treturn *posn;\n+\t//printf(\"# add changeset %lu in %s\\n\", number, codeline->path);\n+\tchangeset = xcalloc(1, sizeof(*changeset));\n+\tchangeset->codeline = codeline;\n+\tchangeset->next = *posn;\n+\tchangeset->previous = prev;\n+\tif (changeset->next)\n+\t\tchangeset->next->previous = changeset;\n+\telse\n+\t\tcodeline->head = changeset;\n+\t*posn = changeset;\n+\tchangeset->number = number;\n+\tcodeline->num_changesets++;\n+\treturn changeset;\n+}\n+\n+static struct p4_changeset *changeset_from_commit(struct p4_depot *depot,\n+\t\t\t\t\t\t  struct commit *commit)\n+{\n+\tunsigned long number = 0;\n+\tchar *codeline = NULL, *field;\n+\tparse_commit(commit);\n+\tfield = strstr(commit->buffer, CHANGE_TAG);\n+\tif (field)\n+\t\tnumber = atoi(field + strlen(CHANGE_TAG));\n+\tfield = strstr(commit->buffer, CODELINE_TAG);\n+\tif (field) {\n+\t\tchar *end;\n+\t\tcodeline = field + strlen(CODELINE_TAG);\n+\t\tend = strchr(codeline, '\\n');\n+\t\tif (end)\n+\t\t\t*end = '\\0';\n+\t}\n+\tif (number && codeline)\n+\t\treturn get_changeset(get_codeline(depot, codeline), number);\n+\treturn NULL;\n+}\n+\n+static struct p4_file *get_file_by_full(struct p4_codeline *codeline,\n+\t\t\t\t\tconst char *fullpath)\n+{\n+\tconst char *rel = fullpath + strlen(codeline->path);\n+\tstruct p4_file **posn;\n+\tfor (posn = &codeline->files; *posn; posn = &(*posn)->next) {\n+\t\tif (!strcmp((*posn)->name, rel))\n+\t\t\treturn *posn;\n+\t}\n+\t*posn = xcalloc(1, sizeof(**posn));\n+\t(*posn)->codeline = codeline;\n+\t(*posn)->name = xstrdup(rel);\n+\treturn *posn;\n+}\n+\n+static struct p4_file *get_related_file(struct p4_file *base, const char *path)\n+{\n+\tint basenamelen = strlen(base->name);\n+\tint reldirlen = strlen(path) - basenamelen;\n+\tstruct p4_codeline *codeline;\n+\tif (reldirlen > 0 && !strcmp(path + reldirlen, base->name)) {\n+\t\t/* File with the same name in another codeline */\n+\t\tchar *other = xstrndup(path, reldirlen);\n+\t\t//printf(\"# find %s in %s\\n\", path, other);\n+\t\tcodeline = get_codeline(base->codeline->depot, other);\n+\t\tfree(other);\n+\t\tif (codeline)\n+\t\t\treturn get_file_by_full(codeline, path);\n+\t\treturn NULL;\n+\t}\n+\tcodeline = find_codeline(base->codeline->depot, path);\n+\tif (codeline) {\n+\t\t/* File with a different name in some known codeline */\n+\t\treturn get_file_by_full(codeline, path);\n+\t}\n+\tfprintf(stderr, \"Trying to identify %s\\n\", path);\n+\t/* Not in any known codeline; need to recheck this after\n+\t * discovering codelines completes.\n+\t */\n+\treturn NULL;\n+}\n+\n+static struct p4_revision *get_revision(struct p4_file *file, unsigned number)\n+{\n+\tstruct p4_revision **posn;\n+\tstruct p4_revision *revision;\n+\tfor (posn = &file->revisions; *posn && (*posn)->number < number;\n+\t     posn = &(*posn)->next)\n+\t\t;\n+\tif (!*posn || (*posn)->number != number) {\n+\t\trevision = xcalloc(1, sizeof(*revision));\n+\t\trevision->next = *posn;\n+\t\t*posn = revision;\n+\t\trevision->number = number;\n+\t\trevision->file = file;\n+\t}\n+\treturn *posn;\n+}\n+\n+static int parse_p4_date(const char *date)\n+{\n+\tstruct tm tm;\n+\tmemset(&tm, 0, sizeof(tm));\n+\ttm.tm_year = strtol(date, NULL, 10) - 1900;\n+\ttm.tm_mon = strtol(date + 5, NULL, 10) - 1;\n+\ttm.tm_mday = strtol(date + 8, NULL, 10);\n+\ttm.tm_hour = strtol(date + 11, NULL, 10);\n+\ttm.tm_min = strtol(date + 14, NULL, 10);\n+\ttm.tm_sec = strtol(date + 17, NULL, 10);\n+\treturn mktime(&tm);\n+}\n+\n+static int is_keyword(const char *text, int keywords)\n+{\n+\tif (!prefixcmp(text, \"Id: \") ||\n+\t    !prefixcmp(text, \"Header: \"))\n+\t\treturn 1;\n+\tif (keywords == 1)\n+\t\treturn 0;\n+\treturn !prefixcmp(text, \"Date: \") ||\n+\t\t!prefixcmp(text, \"DateTime: \") ||\n+\t\t!prefixcmp(text, \"Change: \") ||\n+\t\t!prefixcmp(text, \"File: \") ||\n+\t\t!prefixcmp(text, \"Revision: \") ||\n+\t\t!prefixcmp(text, \"Author: \");\n+}\n+\n+static void handle_keywords(struct strbuf *buf, int keywords)\n+{\n+\tint posn = 0;\n+\tchar *keyword;\n+\n+\tif (!keywords)\n+\t\treturn;\n+\n+\tdo {\n+\t\tkeyword = strchr(buf->buf + posn, '$');\n+\t\tif (!keyword)\n+\t\t\tbreak;\n+\t\tif (!is_keyword(keyword + 1, keywords)) {\n+\t\t\tposn = keyword - buf->buf + 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tchar *eok = strchr(keyword + 1, ':');\n+\t\tsize_t kwl = strcspn(eok, \"$\\n\");\n+\t\tif (!eok[kwl])\n+\t\t\tbreak;\n+\t\tif (eok[kwl] == '$') {\n+\t\t\tstrbuf_remove(buf, eok - buf->buf, kwl);\n+\t\t} else {\n+\t\t\tposn = eok - buf->buf + kwl + 1;\n+\t\t}\n+\t} while (1);\n+}\n+\n+static const char *get_file_type(const char *text, size_t len)\n+{\n+\tif (len == 5 && !memcmp(text, \"ktext\", 5))\n+\t\treturn \"text+k\";\n+\tif (len == 5 && !memcmp(text, \"xtext\", 5))\n+\t\treturn \"text+x\";\n+\tif (len == 6 && !memcmp(text, \"kxtext\", 6))\n+\t\treturn \"text+kx\";\n+\treturn xstrndup(text, len);\n+}\n+\n+static const char *get_file_mode(const char *type)\n+{\n+\tchar *p = strchr(type, '+');\n+\tif (!strcmp(type, \"symlink\"))\n+\t\treturn \"120000\";\n+\tif (p && strchr(p, 'x'))\n+\t\treturn \"100755\";\n+\treturn \"100644\";\n+}\n+\n+static int keywords(const char *type)\n+{\n+\tchar *p = strchr(type, '+');\n+\tif (p) {\n+\t\tchar *k = strchr(p, 'k');\n+\t\tif (k) {\n+\t\t\tif (k[1] == 'o')\n+\t\t\t\treturn 1;\n+\t\t\treturn 2;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n+static void output_data(struct strbuf *buf)\n+{\n+\tprintf(\"data %d\\n\", buf->len);\n+\tfwrite(buf->buf, 1, buf->len, stdout);\n+\tprintf(\"\\n\");\n+}\n+\n+static int write_blob(struct p4_codeline *codeline,\n+\t\t      const unsigned char *sha1,\n+\t\t      const char *path)\n+{\n+\tstruct strbuf buf;\n+\tvoid *content;\n+\tenum object_type type;\n+\tunsigned long size;\n+\tint fd;\n+\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addf(&buf, \"%s/%s\", codeline->working, path);\n+\tcontent = read_sha1_file(sha1, &type, &size);\n+\tfd = open(buf.buf, O_WRONLY | O_CREAT, 0666);\n+\tif (fd < 0) {\n+\t\tdie(\"Got err %d\", errno);\n+\t}\n+\twrite_or_die(fd, content, size);\n+\treturn 0;\n+}\n+\n+/** P4 operations **/\n+\n+static int p4_where(struct p4_codeline *codeline)\n+{\n+\tint fds[2];\n+\tchar *argv[1];\n+\tstruct strbuf buf;\n+\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addstr(&buf, codeline->path);\n+\targv[0] = buf.buf;\n+\tp4_call(fds, \"where\", 1, argv);\n+\tFILE *input = fdopen(fds[1], \"r\");\n+\n+\twhile (!strbuf_getline(&buf, input, '\\n')) {\n+\t\tchar *working = strrchr(buf.buf, ' ');\n+\t\tif (working)\n+\t\t\tcodeline->working = xstrdup(working + 1);\n+\t}\n+\tp4_complete();\n+\treturn codeline->working ? 0 : -1;\n+}\n+\n+static void p4_sync(struct p4_codeline *codeline)\n+{\n+\tchar *argv[1];\n+\tstruct strbuf buf;\n+\n+\tprintf(\"progress syncing %s/...\\n\", codeline->working);\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addf(&buf, \"%s/...@%lu\",\n+\t\t    codeline->working, codeline->head->number);\n+\targv[0] = buf.buf;\n+\tp4_call(NULL, \"sync\", 1, argv);\n+\tp4_complete();\n+}\n+\n+static void p4_edit(struct p4_codeline *codeline, const char *path)\n+{\n+\tchar *argv[1];\n+\tstruct strbuf buf;\n+\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addf(&buf, \"%s/%s\", codeline->working, path);\n+\targv[0] = buf.buf;\n+\tp4_call(NULL, \"edit\", 1, argv);\n+\tstrbuf_release(&buf);\n+\tp4_complete();\n+}\n+\n+static void p4_add(struct p4_codeline *codeline, const char *path)\n+{\n+\tchar *argv[1];\n+\tstruct strbuf buf;\n+\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addf(&buf, \"%s/%s\", codeline->working, path);\n+\targv[0] = buf.buf;\n+\tp4_call(NULL, \"add\", 1, argv);\n+\tstrbuf_release(&buf);\n+\tp4_complete();\n+}\n+\n+static void p4_delete(struct p4_codeline *codeline, const char *path)\n+{\n+\tchar *argv[1];\n+\tstruct strbuf buf;\n+\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addf(&buf, \"%s/%s\", codeline->working, path);\n+\targv[0] = buf.buf;\n+\tp4_call(NULL, \"delete\", 1, argv);\n+\tstrbuf_release(&buf);\n+\tp4_complete();\n+}\n+\n+static void p4_submit(struct commit *commit)\n+{\n+\tint fds[2];\n+\tchar *argv[1];\n+\tint skip = 0;\n+\targv[0] = \"-o\";\n+\tp4_call(fds, \"change\", 1, argv);\n+\n+\tstruct strbuf message;\n+\tstruct strbuf line;\n+\n+\tFILE *input = fdopen(fds[1], \"r\");\n+\n+\tstrbuf_init(&message, 0);\n+\tstrbuf_init(&line, 0);\n+\n+\twhile (!strbuf_getline(&line, input, '\\n')) {\n+\t\tif (!skip) {\n+\t\t\tstrbuf_addstr(&message, line.buf);\n+\t\t\tstrbuf_addch(&message, '\\n');\n+\t\t}\n+\t\tif (line.buf[0] != '\\t')\n+\t\t\tskip = 0;\n+\t\tif (!strcmp(line.buf, \"Description:\")) {\n+\t\t\tchar *posn;\n+\t\t\tparse_commit(commit);\n+\t\t\tposn = strstr(commit->buffer, \"\\n\\n\");\n+\t\t\tif (posn)\n+\t\t\t\tposn += 2;\n+\t\t\twhile (*posn) {\n+\t\t\t\tchar *eol = strchr(posn, '\\n');\n+\t\t\t\tstrbuf_addstr(&message, \"\\t\");\n+\t\t\t\tif (eol) {\n+\t\t\t\t\teol++;\n+\t\t\t\t\tstrbuf_add(&message, posn, eol - posn);\n+\t\t\t\t\tposn = eol;\n+\t\t\t\t} else {\n+\t\t\t\t\tstrbuf_addstr(&message, posn);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tstrbuf_addstr(&message, \"\\n\");\n+\t\t\tskip = 1;\n+\t\t}\n+\t}\n+\n+\tfclose(input);\n+\tp4_complete();\n+\n+\tprintf(\"%s\\n\", message.buf);\n+\n+\targv[0] = \"-i\";\n+\tp4_call(fds, \"submit\", 1, argv);\n+\n+\twrite_or_die(fds[0], message.buf, message.len);\n+\tclose(fds[0]);\n+\n+\tinput = fdopen(fds[1], \"r\");\n+\twhile (!strbuf_getline(&line, input, '\\n'))\n+\t\tfprintf(stderr, \"%s\\n\", line.buf);\n+\tp4_complete();\n+}\n+\n+static void p4_print(struct p4_revision *revision)\n+{\n+\tchar *argv[2];\n+\tstruct strbuf line;\n+\tstrbuf_init(&line, 0);\n+\tstrbuf_addf(&line, \"%s%s#%lu\",\n+\t\t    revision->file->codeline->path,\n+\t\t    revision->file->name, revision->number);\n+\targv[1] = strdup(line.buf);\n+\targv[0] = \"-q\";\n+\n+\tif (LIST_P4_OPERATIONS)\n+\t\tfprintf(stderr, \"p4 print\\n\");\n+\n+\tstrbuf_reset(&line);\n+\n+\tp4_call_buffer(\"print\", 2, argv, &line, strbuf_add);\n+\n+\tfree(argv[1]);\n+\n+\thandle_keywords(&line, keywords(revision->type));\n+\n+\t/* Perforce puts a newline at the end when printing symlinks */\n+\tif (!strcmp(revision->type, \"symlink\"))\n+\t\tline.len--;\n+\n+\toutput_data(&line);\n+\n+\tstrbuf_release(&line);\n+\n+\tprints_done++;\n+}\n+\n+struct p4_change_data {\n+\tstruct p4_changeset *changeset;\n+\tint date;\n+\tchar *user;\n+\tstruct strbuf message;\n+};\n+\n+static void p4_change_cb(struct p4_change_data *data, const char *key,\n+\t\t\t const char *value)\n+{\n+\tif (!strcmp(key, \"User\"))\n+\t\tdata->user = xstrdup(value);\n+\telse if (!strcmp(key, \"Date\"))\n+\t\tdata->date = parse_p4_date(value);\n+\telse if (!strcmp(key, \"Description\"))\n+\t\tstrbuf_addstr(&data->message, value);\n+}\n+\n+static void p4_change(struct p4_changeset *changeset)\n+{\n+\tchar *argv[2];\n+\tstruct strbuf line;\n+\n+\tstruct p4_change_data data = {\n+\t\t.changeset = changeset,\n+\t\t.date = 0,\n+\t\t.user = NULL,\n+\t};\n+\n+\tif (LIST_P4_OPERATIONS)\n+\t\tfprintf(stderr, \"p4 change\\n\");\n+\n+\tstrbuf_init(&data.message, 0);\n+\n+\tstrbuf_init(&line, 0);\n+\tstrbuf_addf(&line, \"%lu\", changeset->number);\n+\targv[1] = line.buf;\n+\targv[0] = \"-o\";\n+\tp4_call_form(\"change\", 2, argv, &data, p4_change_cb);\n+\tstrbuf_release(&line);\n+\n+\tprintf(\"committer %s <%s> %d +0000\\n\",\n+\t       data.user, data.user, data.date);\n+\tfree(data.user);\n+\n+\tstrbuf_addf(&data.message,\n+\t\t    \"\\n\" CODELINE_TAG \"%s\\n\" CHANGE_TAG \"%lu\\n\",\n+\t\t    changeset->codeline->path, changeset->number);\n+\toutput_data(&data.message);\n+\tstrbuf_release(&data.message);\n+}\n+\n+struct p4_filelog_data {\n+\tstruct p4_codeline *codeline;\n+\tstruct p4_file *file;\n+\tstruct p4_revision *revision;\n+};\n+\n+static void p4_filelog_cb(struct p4_filelog_data *data,\n+\t\t\t  char level, const char *line)\n+{\n+\tif (level == 0) {\n+\t\tdata->file = get_file_by_full(data->codeline, line);\n+\t} else if (level == 1) {\n+\t\tint rev, change, delete = 0, branch = 0;\n+\t\tchar *posn;\n+\t\trev = strtoul(line + 1, &posn, 10);  /* skip the '#' */\n+\t\tposn += strlen(\" change \");\n+\t\tchange = strtoul(posn, &posn, 10);\n+\t\tif (!prefixcmp(posn, \" delete\"))\n+\t\t\tdelete = 1;\n+\t\tif (!prefixcmp(posn, \" branch\"))\n+\t\t\tbranch = 1;\n+\t\tposn = strchr(posn, '(') + 1;\n+\t\tdata->revision = get_revision(data->file, rev);\n+\t\tdata->revision->changeset =\n+\t\t\tget_changeset(data->codeline, change);\n+\t\tdata->revision->type = get_file_type(posn,\n+\t\t\t\t\t\t     strchr(posn, ')') - posn);\n+\t\tdata->revision->delete = delete;\n+\t\tdata->revision->branch = branch;\n+\t\tadd_to_revision_list(&data->revision->changeset->revisions,\n+\t\t\t\t     data->revision);\n+\t} else if (level == 2) {\n+\t\tconst char *path;\n+\t\tint rev, from = 0;\n+\t\tchar *type = xstrdup(line);\n+\t\tchar *posn = strrchr(type, ' ') + 1;\n+\n+\t\tfrom = (!prefixcmp(type, \"ignored\") &&\n+\t\t\tposn == type + strlen(\"ignored\") + 1) ||\n+\t\t\t!prefixcmp(strchr(type, ' '), \" from\");\n+\n+\t\tpath = posn;\n+\t\tposn = strchr(posn, '#');\n+\t\t*(posn++) = '\\0';\n+\t\tdo {\n+\t\t\t/* ???? What does a list of revisions mean? */\n+\t\t\trev = strtoul(posn, &posn, 10);\n+\t\t\tif (*posn != ',')\n+\t\t\t\tbreak;\n+\t\t\tposn += 2;\n+\t\t} while (1);\n+\t\tif (from) {\n+\t\t\tstruct p4_file *rel_file =\n+\t\t\t\tget_related_file(data->file, path);\n+\t\t\tif (!rel_file) {\n+\t\t\t\t/*\n+\t\t\t\tprintf(\"# Couldn't find %s related to %s %s\\n\",\n+\t\t\t\t       path, data->file->codeline->path,\n+\t\t\t\t       data->file->name);\n+\t\t\t\t*/\n+\t\t\t}\n+\t\t\tif (rel_file && rel_file->codeline != data->codeline)\n+\t\t\t\tadd_to_revision_list(&data->revision->integrated,\n+\t\t\t\t\t\t     get_revision(rel_file, rev));\n+\t\t} else if (find_new_codelines) {\n+\t\t\t/* This is an \"<op> into <path>#<rev>\" line.\n+\t\t\t * We just want to try to create a codeline.\n+\t\t\t */\n+\t\t\tget_related_file(data->file, path);\n+\t\t}\n+\t\tfree(type);\n+\t}\n+}\n+\n+/** Finds all files in the codeline, and all revisions of those files,\n+ * and all of the changesets they are from, and looks up the codelines\n+ * and files they integrate or branch.\n+ **/\n+static void p4_filelog(struct p4_codeline *codeline)\n+{\n+\tstruct strbuf line;\n+\n+\tstruct p4_filelog_data data = {\n+\t\t.codeline = codeline,\n+\t\t.file = NULL,\n+\t\t.revision = NULL\n+\t};\n+\tchar *arg;\n+\n+\tif (codeline->filelog_done)\n+\t\treturn;\n+\n+\tif (LIST_P4_OPERATIONS)\n+\t\tfprintf(stderr, \"p4 filelog %s\\n\", codeline->path);\n+\n+\tstrbuf_init(&line, 0);\n+\tstrbuf_addstr(&line, codeline->path);\n+\tstrbuf_addstr(&line, \"/...\");\n+\targ = line.buf;\n+\tp4_call_info(\"filelog\", 1, &arg, &data, p4_filelog_cb);\n+\tstrbuf_release(&line);\n+\tif (codeline->history)\n+\t\tcodeline->unreported = codeline->history->next;\n+\telse\n+\t\tcodeline->unreported = codeline->changesets;\n+\tcodeline->filelog_done = 1;\n+}\n+\n+/** Functions to import things (i.e., fill out the representations) **/\n+\n+static struct p4_changeset_list *\n+find_codeline_changeset(struct p4_changeset_list **list,\n+\t\t\tstruct p4_codeline *codeline)\n+{\n+\twhile (*list) {\n+\t\tif ((*list)->changeset->codeline == codeline)\n+\t\t\treturn *list;\n+\t\tlist = &(*list)->next;\n+\t}\n+\t*list = xcalloc(1, sizeof(**list));\n+\treturn *list;\n+}\n+\n+static void resolve_codeline_contents(struct p4_codeline *codeline)\n+{\n+\tstruct p4_revision_list *prevrevs = NULL;\n+\tstruct p4_changeset *changeset = codeline->changesets;\n+\twhile (changeset) {\n+\t\tstruct p4_revision_list *changes =\n+\t\t\tcopy_revision_list(changeset->revisions);\n+\t\tchangeset->contents = changes;\n+\t\twhile (prevrevs) {\n+\t\t\tstruct p4_revision_list *posn;\n+\t\t\tint found = 0;\n+\t\t\tfor (posn = changes; posn; posn = posn->next) {\n+\t\t\t\tif (prevrevs->revision->file ==\n+\t\t\t\t    posn->revision->file) {\n+\t\t\t\t\tfound = 1;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif (!found) {\n+\t\t\t\tstruct p4_revision_list *item =\n+\t\t\t\t\txcalloc(1, sizeof(*item));\n+\t\t\t\titem->revision = prevrevs->revision;\n+\t\t\t\titem->next = changeset->contents;\n+\t\t\t\tchangeset->contents = item;\n+\t\t\t}\n+\t\t\tprevrevs = prevrevs->next;\n+\t\t}\n+\n+\t\tprevrevs = changeset->contents;\n+\t\tchangeset = changeset->next;\n+\t}\n+}\n+\n+static void resolve_changeset_integrates(struct p4_changeset *changeset)\n+{\n+\tstruct p4_revision_list *posn;\n+\tstruct p4_changeset_list *changesets = NULL;\n+\t/* For each codeline, we want the highest numbered changeset\n+\t * that introduced a revision that has been integrated.\n+\t */\n+\tfor (posn = changeset->revisions; posn; posn = posn->next) {\n+\t\tstruct p4_revision_list *rev_ints = posn->revision->integrated;\n+\t\twhile (rev_ints) {\n+\t\t\tstruct p4_changeset_list *item;\n+\t\t\tif (rev_ints->revision->file->codeline == changeset->codeline) {\n+\t\t\t\trev_ints = rev_ints->next;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\t/* The revision doesn't have the changeset\n+\t\t\t * filled out unless we call this.\n+\t\t\t */\n+\t\t\tp4_filelog(rev_ints->revision->file->codeline);\n+\t\t\titem = find_codeline_changeset(&changesets,\n+\t\t\t\t\t\t       rev_ints->revision->file->codeline);\n+\t\t\tif (!item->changeset ||\n+\t\t\t    item->changeset->number < rev_ints->revision->changeset->number) {\n+\t\t\t\tif (0)\n+\t\t\t\t\tprintf(\"progress %lu integrates %s#%lu from %lu\\n\",\n+\t\t\t\t\t       changeset->number,\n+\t\t\t\t\t       rev_ints->revision->file->name,\n+\t\t\t\t\t       rev_ints->revision->number,\n+\t\t\t\t\t       rev_ints->revision->changeset->number);\n+\t\t\t\titem->changeset = rev_ints->revision->changeset;\n+\t\t\t}\n+\t\t\trev_ints = rev_ints->next;\n+\t\t}\n+\t}\n+\t/* We could issue a warning if the state of other files didn't\n+\t * match and yet didn't get integrated, but that's a lot of\n+\t * work and there's no good way to represent the case of a\n+\t * commit contributing to but not being completely obsoleted\n+\t * by another commit.\n+\t */\n+\tchangeset->integrated = changesets;\n+\twhile (changesets) {\n+\t\t//printf(\"# integrate %lu from %lu\\n\", changeset->number, changesets->changeset->number);\n+\t\tchangesets = changesets->next;\n+\t}\n+}\n+\n+static void follow_codeline(struct p4_codeline *target)\n+{\n+\tstruct p4_codeline *posn;\n+\tif (target->filelog_done)\n+\t\treturn;\n+\n+\tp4_filelog(target);\n+\n+\tif (0)\n+\t\tprintf(\"progress resolving integrates\\n\");\n+\n+\t/* Now resolve all the integrates in changesets */\n+\tfor (posn = target->depot->codelines; posn; posn = posn->next) {\n+\t\tstruct p4_changeset *changeset;\n+\t\tfor (changeset = posn->unreported; changeset; changeset = changeset->next) {\n+\t\t\tresolve_changeset_integrates(changeset);\n+\t\t}\n+\t\tresolve_codeline_contents(posn);\n+\t}\n+}\n+\n+static struct p4_codeline *import_depot(struct p4_depot *depot, const char *refname)\n+{\n+\tstruct p4_codeline *target;\n+\tchar *path = refname_to_codeline(refname);\n+\ttarget = get_codeline(depot, path);\n+\n+\tif (!target)\n+\t\tdie(\"Invalid codeline: %s\", path);\n+\n+\tfree(path);\n+\n+\tfollow_codeline(target);\n+\n+\treturn target;\n+}\n+\n+static void name_changeset(struct p4_changeset *changeset)\n+{\n+\tif (changeset->commit)\n+\t\tprintf(\"%s\\n\", sha1_to_hex(changeset->commit->object.sha1));\n+\telse\n+\t\tprintf(\":%d\\n\", changeset->mark);\n+}\n+\n+static void lookup_git_changeset(struct p4_codeline *codeline,\n+\t\t\t\t struct p4_changeset *changeset)\n+{\n+\twhile (!changeset->commit) {\n+\t\tstruct commit *parent = codeline->history->commit->parents->item;\n+\t\tparse_commit(parent);\n+\t\tcodeline->history->previous->commit = parent;\n+\t\tcodeline->history = codeline->history->previous;\n+\t}\n+}\n+\n+static void report_codeline(struct p4_codeline *codeline,\n+\t\t\t    struct p4_changeset *until);\n+\n+static void identify_changeset(struct p4_changeset *changeset)\n+{\n+\tif (changeset->mark || changeset->commit)\n+\t\treturn;\n+\tif (changeset->codeline->finished_changeset >= changeset->number)\n+\t\tlookup_git_changeset(changeset->codeline, changeset);\n+\telse\n+\t\treport_codeline(changeset->codeline, changeset);\n+}\n+\n+static int skip_found(struct p4_revision *revision,\n+\t\t      struct p4_revision_list **origin) {\n+\tstruct p4_revision *orev = NULL;\n+\tstruct p4_revision_list *i;\n+\twhile (*origin) {\n+\t\tif (!strcmp((*origin)->revision->file->name,\n+\t\t\t    revision->file->name)) {\n+\t\t\tstruct p4_revision_list *oitem = *origin;\n+\t\t\t*origin = oitem->next;\n+\t\t\torev = oitem->revision;\n+\t\t\tfree(oitem);\n+\t\t\tbreak;\n+\t\t} else {\n+\t\t\torigin = &((*origin)->next);\n+\t\t}\n+\t}\n+\tif (!revision->branch) /* It's changed anyway */\n+\t\treturn 0;\n+\tfor (i = revision->integrated; i; i = i->next) {\n+\t\tif (i->revision == orev)\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+static void report_codeline(struct p4_codeline *codeline, struct p4_changeset *until)\n+{\n+\tstruct p4_changeset *changeset;\n+\tstruct p4_revision_list *rev;\n+\n+\tprintf(\"progress import codeline %s (%lu-%lu)\\n\", codeline->path,\n+\t       codeline->unreported->number, until->number);\n+\n+\tfor (changeset = codeline->unreported; changeset; changeset = changeset->next) {\n+\t\tstruct p4_changeset_list *integrated = changeset->integrated;\n+\t\tstruct p4_revision_list *origin = NULL;\n+\n+\t\twhile (integrated) {\n+\t\t\tidentify_changeset(integrated->changeset);\n+\t\t\tintegrated = integrated->next;\n+\t\t}\n+\t\tprintf(\"progress import changeset %lu (%s)\\n\",\n+\t\t       changeset->number, changeset->codeline->path);\n+\t\tprintf(\"# changeset %lu\\n\", changeset->number);\n+\t\tprintf(\"commit %s\\n\", codeline->refname);\n+\t\tchangeset->mark = codeline->depot->next_mark++;\n+\t\tprintf(\"mark :%d\\n\", changeset->mark);\n+\t\tp4_change(changeset);\n+\t\tintegrated = changeset->integrated;\n+\t\tif (changeset->previous) {\n+\t\t\tprintf(\"from \");\n+\t\t\tname_changeset(changeset->previous);\n+\t\t} else if (integrated) {\n+\t\t\tprintf(\"from \");\n+\t\t\torigin = copy_revision_list(integrated->changeset->contents);\n+\t\t\tname_changeset(integrated->changeset);\n+\t\t\tintegrated = integrated->next;\n+\t\t}\n+\n+\t\twhile (integrated) {\n+\t\t\tprintf(\"merge \");\n+\t\t\tname_changeset(integrated->changeset);\n+\t\t\tintegrated = integrated->next;\n+\t\t}\n+\n+\t\tfor (rev = changeset->revisions; rev; rev = rev->next) {\n+\t\t\tif (rev->revision->delete) {\n+\t\t\t\tprintf(\"D %s\\n\", rev->revision->file->name + 1);\n+\t\t\t} else if (!skip_found(rev->revision, &origin)) {\n+\t\t\t\tprintf(\"M %s inline %s\\n\",\n+\t\t\t\t       get_file_mode(rev->revision->type),\n+\t\t\t\t       rev->revision->file->name + 1);\n+\t\t\t\tp4_print(rev->revision);\n+\t\t\t}\n+\t\t}\n+\t\twhile (origin) {\n+\t\t\tstruct p4_revision_list *old;\n+\t\t\tprintf(\"D %s\\n\", origin->revision->file->name + 1);\n+\t\t\told = origin;\n+\t\t\torigin = origin->next;\n+\t\t\tfree(old);\n+\t\t}\n+\t\tprintf(\"\\n\");\n+\t\tcodeline->unreported = changeset->next;\n+\t\tif (changeset == until)\n+\t\t\tbreak;\n+\t}\n+\tprintf(\"checkpoint\\n\");\n+}\n+\n+static void import_p4(int ref_nr, const char **refs)\n+{\n+\tint i;\n+\tstruct p4_depot *depot = get_depot();\n+\tstruct p4_codeline *target;\n+\tsave_commit_buffer = 1;\n+\n+\tfor (i = 0; i < ref_nr; i++) {\n+\t\ttarget = import_depot(depot, refs[i]);\n+\n+\t\tidentify_changeset(target->head);\n+\t}\n+}\n+\n+static void export_change(struct diff_options *options,\n+\t\t\t  unsigned old_mode, unsigned new_mode,\n+\t\t\t  const unsigned char *old_sha1,\n+\t\t\t  const unsigned char *new_sha1,\n+\t\t\t  const char *path)\n+{\n+\tstruct p4_codeline *codeline = options->format_callback_data;\n+\tp4_edit(codeline, path);\n+\twrite_blob(codeline, new_sha1, path);\n+}\n+\n+static void export_add_remove(struct diff_options *options,\n+\t\t\t      int addremove, unsigned mode,\n+\t\t\t      const unsigned char *sha1,\n+\t\t\t      const char *path)\n+{\n+\tstruct p4_codeline *codeline = options->format_callback_data;\n+\tif (addremove == '+') {\n+\t\twrite_blob(codeline, sha1, path);\n+\t\tp4_add(codeline, path);\n+\t} else if (addremove == '-') {\n+\t\tp4_delete(codeline, path);\n+\t}\n+}\n+\n+static void export_commit(struct p4_codeline *codeline,\n+\t\t\t  struct commit *git_commit, struct commit *git_parent)\n+{\n+\tstruct tree_desc pre, post;\n+\tstruct diff_options opts;\n+\tmemset(&opts, 0, sizeof(opts));\n+\tparse_tree(git_commit->tree);\n+\tparse_tree(git_parent->tree);\n+\tinit_tree_desc(&pre, git_parent->tree->buffer, git_parent->tree->size);\n+\tinit_tree_desc(&post, git_commit->tree->buffer, git_commit->tree->size);\n+\topts.change = export_change;\n+\topts.add_remove = export_add_remove;\n+\topts.format_callback_data = codeline;\n+\topts.flags = DIFF_OPT_RECURSIVE;\n+\tdiff_tree(&pre, &post, \"/\", &opts);\n+\tp4_submit(git_commit);\n+}\n+\n+static void export_p4(struct remote *remote, const char *branch)\n+{\n+\tstruct p4_depot *depot = get_depot();\n+\tconst char *codeline = remote->url[0];\n+\tstruct p4_codeline *target;\n+\tstruct strbuf buf;\n+\n+\t// check client\n+\n+\ttarget = import_depot(depot, codeline);\n+\n+\tstrbuf_init(&buf, 0);\n+\n+\twhile (!strbuf_getline(&buf, stdin, '\\n')) {\n+\t\tstruct p4_changeset *parent = NULL, *integrate = NULL;\n+\t\tunsigned char sha1[20];\n+\t\tstruct commit *commit, *git_parent = NULL;\n+\t\tstruct commit_list *parents;\n+\t\tget_sha1(buf.buf, sha1);\n+\t\tcommit = lookup_commit(sha1);\n+\t\tparse_commit(commit);\n+\t\tfor (parents = commit->parents; parents; parents = parents->next) {\n+\t\t\tstruct p4_changeset *p4_parent =\n+\t\t\t\tchangeset_from_commit(depot, parents->item);\n+\t\t\tif (p4_parent) {\n+\t\t\t\tif (p4_parent->codeline == target) {\n+\t\t\t\t\tparent = p4_parent;\n+\t\t\t\t\tgit_parent = parents->item;\n+\t\t\t\t} else\n+\t\t\t\t\tintegrate = p4_parent;\n+\t\t\t}\n+\t\t}\n+\t\tif (target->head != parent) {\n+\t\t\tprintf(\"progress not up-to-date\\n\");\n+\t\t\treturn;\n+\t\t}\n+\t\tif (p4_where(target))\n+\t\t\tbreak;\n+\t\tp4_sync(target);\n+\n+\t\tif (!parent) {\n+\t\t\t// Need to start new codeline\n+\t\t}\n+\t\texport_commit(target, commit, git_parent);\n+\t}\n+}\n+\n+static const char **env;\n+static int env_nr;\n+static int env_alloc;\n+\n+static const char **codelines;\n+static int codeline_nr;\n+static int codeline_alloc;\n+\n+static int handle_config(const char *key, const char *value, void *cb)\n+{\n+\tstruct remote *remote = cb;\n+\tstruct strbuf buf;\n+\tconst char *subkey = NULL;\n+\n+\tif (!prefixcmp(key, \"vcs-p4.\"))\n+\t\tsubkey = key + strlen(\"vcs-p4.\");\n+\n+\tif (remote && !prefixcmp(key, \"remote.\") &&\n+\t    !prefixcmp(key + strlen(\"remote.\"), remote->name))\n+\t    subkey = key + strlen(\"remote.\") + strlen(remote->name) + 1;\n+\n+\tif (!subkey)\n+\t\treturn 0;\n+\n+\tif (!strcmp(subkey, \"findbranches\")) {\n+\t\tfind_new_codelines = git_config_bool(key, value);\n+\t}\n+\tif (!strcmp(subkey, \"ignorecodeline\")) {\n+\t\tALLOC_GROW(ignore_codelines, ignore_codeline_nr + 1,\n+\t\t\t   ignore_codeline_alloc);\n+\t\tignore_codelines[ignore_codeline_nr++] = xstrdup(value);\n+\t}\n+\tif (!strcmp(subkey, \"port\")) {\n+\t\tstrbuf_init(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"P4PORT=%s\", value);\n+\n+\t\tALLOC_GROW(env, env_nr + 1, env_alloc);\n+\t\tenv[env_nr++] = strbuf_detach(&buf, NULL);\n+\t}\n+\tif (!strcmp(subkey, \"client\")) {\n+\t\tstrbuf_init(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"P4CLIENT=%s\", value);\n+\n+\t\tALLOC_GROW(env, env_nr + 1, env_alloc);\n+\t\tenv[env_nr++] = strbuf_detach(&buf, NULL);\n+\t}\n+\tif (!strcmp(subkey, \"codelineformat\")) {\n+\t\tcodeline_regex = (regex_t*)xmalloc(sizeof(regex_t));\n+\t\tif (regcomp(codeline_regex, value, REG_EXTENDED)) {\n+\t\t\tfree(codeline_regex);\n+\t\t\tfprintf(stderr, \"Invalid codeline pattern: %s\",\n+\t\t\t\tvalue);\n+\t\t}\n+\t}\n+\tif (!strcmp(subkey, \"codeline\")) {\n+\t\tALLOC_GROW(codelines, codeline_nr + 1, codeline_alloc);\n+\t\tcodelines[codeline_nr++] = xstrdup(value);\n+\t}\n+\treturn 0;\n+}\n+\n+int main(int argc, const char **argv)\n+{\n+\tconst char *prefix = NULL;\n+\tstruct remote *remote;\n+\n+\tif (argc < 2) {\n+\t\tfprintf(stderr, \"Command needed\");\n+\t\treturn 1;\n+\t}\n+\n+\tif (!strcmp(argv[1], \"capabilities\")) {\n+\t\tgit_config(handle_config, NULL);\n+\n+\t\tprintf(\"import\\n\");\n+\t\tprintf(\"find-new-branches\\n\");\n+\t\tprintf(\"export\\n\");\n+\t\tprintf(\"fork\\n\");\n+\t\tprintf(\"merge\\n\");\n+\t\treturn 0;\n+\t}\n+\n+\tif (!strcmp(argv[1], \"import\")) {\n+\t\tprefix = setup_git_directory();\n+\t\tremote = remote_get(argv[2]);\n+\n+\t\tgit_config(handle_config, remote);\n+\n+\t\tfind_new_codelines = 0;\n+\n+\t\tALLOC_GROW(env, env_nr + 1, env_alloc);\n+\t\tenv[env_nr++] = NULL;\n+\n+\t\tp4_init(env);\n+\n+\t\timport_p4(argc - 3, argv + 3);\n+\t\tp4_fini();\n+\t\tif (LIST_P4_OPERATIONS)\n+\t\t\tfprintf(stderr, \"Prints done: %d\\n\", prints_done);\n+\t\treturn 0;\n+\t}\n+\tif (!strcmp(argv[1], \"list\")) {\n+\t\tint i;\n+\n+\t\tprefix = setup_git_directory();\n+\t\tremote = remote_get(argv[2]);\n+\n+\t\tgit_config(handle_config, remote);\n+\n+\t\tALLOC_GROW(env, env_nr + 1, env_alloc);\n+\t\tenv[env_nr++] = NULL;\n+\n+\t\tif (find_new_codelines) {\n+\t\t\tstruct p4_depot *depot = get_depot();\n+\t\t\tstruct p4_codeline *codeline;\n+\t\t\tsave_commit_buffer = 1;\n+\n+\t\t\tp4_init(env);\n+\n+\t\t\tfor (i = 0; i < codeline_nr; i++)\n+\t\t\t\timport_depot(depot,\n+\t\t\t\t\t     codeline_to_refname(codelines[i]));\n+\n+\t\t\tfor (codeline = depot->codelines; codeline;\n+\t\t\t     codeline = codeline->next) {\n+\t\t\t\tif (codeline->ignore)\n+\t\t\t\t\tcontinue;\n+\t\t\t\tfollow_codeline(codeline);\n+\t\t\t\tprintf(\"%s %s\\n\", codeline->refname,\n+\t\t\t\t       codeline->head == codeline->history ?\n+\t\t\t\t       \"unchanged\" : \"changed\");\n+\t\t\t}\n+\n+\t\t\tp4_fini();\n+\t\t} else {\n+\t\t\tfor (i = 0; i < codeline_nr; i++)\n+\t\t\t\tprintf(\"%s\\n\",\n+\t\t\t\t       codeline_to_refname(codelines[i]));\n+\t\t}\n+\t\treturn 0;\n+\t}\n+\tif (!strcmp(argv[1], \"export\")) {\n+\t\tprefix = setup_git_directory();\n+\n+\t\tremote = remote_get(argv[2]);\n+\n+\t\tgit_config(handle_config, remote);\n+\n+\t\tALLOC_GROW(env, env_nr + 1, env_alloc);\n+\t\tenv[env_nr++] = NULL;\n+\n+\t\tp4_init(env);\n+\n+\t\texport_p4(remote, argv[3]);\n+\t\t// 1: check whether the import of the target location\n+\t\t//    is up-to-date\n+\n+\t\t// 2: find the target location in the client view\n+\n+\t\t// 3: bring the client view up-to-date with the target\n+\t\t//    location\n+\n+\t\t// 4: recheck that this matches the tree\n+\n+\t\t// 5: open the necessary files in the client\n+\n+\t\t// 6: replace the necessary files in the filesystem\n+\n+\t\t// 7: submit\n+\n+\t\t// 8: reimport\n+\n+\t\t// 9: go back to (3)\n+\t\tp4_fini();\n+\t}\n+\treturn 1;\n+}\ndiff --git a/vcs-p4/vcs-p4.h b/vcs-p4/vcs-p4.h\nnew file mode 100644\nindex 0000000..57ad475\n--- /dev/null\n+++ b/vcs-p4/vcs-p4.h\n@@ -0,0 +1,128 @@\n+#ifndef VCS_P4_H\n+#define VCS_P4_H\n+\n+struct p4_depot {\n+\tstruct p4_codeline *codelines;\n+\n+\tint next_mark;\n+};\n+\n+/** Note that multiple codelines can have changesets with the same\n+ * number.\n+ **/\n+struct p4_changeset {\n+\tstruct p4_codeline *codeline;\n+\n+\tunsigned long number;\n+\n+\t/** Used only if a previous import found this changeset **/\n+\tstruct commit *commit;\n+\n+\t/** Used only if this changeset is newly imported in this operation. **/\n+\tint mark;\n+\n+\tconst char *message;\n+\n+\t/** These are the revisions introduced in the changeset **/\n+\tstruct p4_revision_list *revisions;\n+\n+\t/** These are the revisions which are current as of the changeset **/\n+\tstruct p4_revision_list *contents;\n+\n+\t/** Not explicit in p4 **/\n+\tstruct p4_changeset_list *integrated;\n+\n+\t/** Next and previous in codeline **/\n+\tstruct p4_changeset *next;\n+\tstruct p4_changeset *previous;\n+};\n+\n+struct p4_changeset_list {\n+\tstruct p4_changeset *changeset;\n+\tstruct p4_changeset_list *next;\n+};\n+\n+struct p4_revision {\n+\tunsigned long number;\n+\n+\tunsigned delete : 1;\n+\tunsigned branch : 1; /* unchanged against something integrated */\n+\n+\tconst char *type;\n+\n+\tstruct p4_file *file;\n+\tstruct p4_changeset *changeset;\n+\n+\tstruct p4_revision_list *integrated;\n+\n+\t/** Next in file **/\n+\tstruct p4_revision *next;\n+};\n+\n+/** Represents a collection of revisions of different files\n+ **/\n+struct p4_revision_list {\n+\tstruct p4_revision *revision;\n+\tstruct p4_revision_list *next;\n+};\n+\n+struct p4_file {\n+\tstruct p4_codeline *codeline;\n+\tconst char *name;\n+\n+\tunsigned head_number;\n+\n+\tstruct p4_revision *revisions;\n+\n+\t/** Next file in codeline **/\n+\tstruct p4_file *next;\n+};\n+\n+/** perforce doesn't record codelines; we have to reverse-engineer\n+ * them from how people seem to be branching.\n+ **/\n+struct p4_codeline {\n+\tunsigned ignore : 1;\n+\n+\tstruct p4_depot *depot;\n+\n+\t/** Base path of codeline **/\n+\tconst char *path;\n+\n+\t/** git refname to import into **/\n+\tconst char *refname;\n+\n+\tstruct p4_file *files;\n+\tstruct p4_changeset *changesets;\n+\n+\tint filelog_done;\n+\n+\t/* The incremental state is that we have some changeset that\n+\t * we previously imported up to, and we have git history going\n+\t * back from that point, of which we've looked up some and\n+\t * could look up more as needed. Also, there's p4-only history\n+\t * going forward after the common history, and we've imported\n+\t * some of that, and could import more as needed. Since\n+\t * codelines are sorted by changeset number, we can tell which\n+\t * way to go to get a name for a changeset.\n+\t */\n+\tstruct p4_changeset *history;\n+\tstruct p4_changeset *unreported;\n+\n+\tstruct p4_changeset *head;\n+\n+\tunsigned long finished_changeset;\n+\n+\t/** For reporting **/\n+\tunsigned long num_changesets;\n+\n+\t/** Next codeline in depot **/\n+\tstruct p4_codeline *next;\n+\n+\t/** Filesystem location of working directory for this codeline\n+\t * on the client.\n+\t **/\n+\tchar *working;\n+};\n+\n+#endif\n-- \n1.6.2.1.476.g9bf04b\n"}]}